From 0bfc1305774afb29770759702fd167d567546104 Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Tue, 20 Jan 2026 20:47:05 +0300 Subject: [PATCH 001/293] Implement OAuth token refresh for collaborative documents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Client proactively refreshes tokens before expiry using session auth, then sends new token to Hocuspocus server via stateless channel. Token strategy: 5min access tokens, refresh at 80% lifetime (~4min), session-based refresh (no refresh tokens needed). Flow: ``` Client OpenProject Hocuspocus │ │ │ │── initial token ─────────────────────┼──────────────────────────────────►│ onAuthenticate │ │ │ │ [timer: 80% of expiry] │ │ │ │ │ │── POST /documents/{id}/oauth/refresh_token ─►│ │ │◄─────────────── {token, expires_at} ─│ │ │ │ │ │── sendStateless(REFRESH:) ────┼──────────────────────────────────►│ onStateless │ │ │ (update context.token) ``` * https://community.openproject.org/wp/68460 * https://community.openproject.org/wp/70362 --- .../documents/init-yjs-provider.controller.ts | 19 +++ .../documents/token-refresh.service.ts | 145 ++++++++++++++++++ .../block_note_editor_component.html.erb | 5 +- .../block_note_editor_component.rb | 6 +- .../oauth/refresh_tokens_controller.rb | 55 +++++++ .../app/controllers/documents_controller.rb | 25 +-- .../documents/oauth/encrypt_token_service.rb | 2 +- .../documents/oauth/generate_token_service.rb | 2 +- .../oauth/token_with_metadata_service.rb | 58 +++++++ .../app/views/documents/show.html.erb | 8 +- modules/documents/config/routes.rb | 4 + .../lib/open_project/documents/engine.rb | 3 +- .../oauth/refresh_tokens_controller_spec.rb | 126 +++++++++++++++ .../oauth/token_with_metadata_service_spec.rb | 92 +++++++++++ 14 files changed, 526 insertions(+), 24 deletions(-) create mode 100644 frontend/src/stimulus/services/documents/token-refresh.service.ts create mode 100644 modules/documents/app/controllers/documents/oauth/refresh_tokens_controller.rb create mode 100644 modules/documents/app/services/documents/oauth/token_with_metadata_service.rb create mode 100644 modules/documents/spec/controllers/documents/oauth/refresh_tokens_controller_spec.rb create mode 100644 modules/documents/spec/services/documents/oauth/token_with_metadata_service_spec.rb diff --git a/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts b/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts index a210c4477ec..ef4dd52fd25 100644 --- a/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts @@ -31,6 +31,7 @@ import { HocuspocusProvider } from '@hocuspocus/provider'; import { Controller } from '@hotwired/stimulus'; import { LiveCollaborationManager } from 'core-stimulus/helpers/live-collaboration-helpers'; +import { TokenRefreshService } from 'core-stimulus/services/documents/token-refresh.service'; import type { Doc } from 'yjs'; import * as Y from 'yjs'; @@ -39,11 +40,19 @@ export default class extends Controller { hocuspocusUrl: String, oauthToken: String, documentName: String, + tokenExpiresAt: String, + tokenExpiresInSeconds: Number, + refreshUrl: String, }; declare readonly hocuspocusUrlValue:string; declare readonly oauthTokenValue:string; declare readonly documentNameValue:string; + declare readonly tokenExpiresAtValue:string; + declare readonly tokenExpiresInSecondsValue:number; + declare readonly refreshUrlValue:string; + + private tokenRefreshService:TokenRefreshService | null = null; connect():void { const ydoc:Doc = new Y.Doc(); @@ -52,12 +61,22 @@ export default class extends Controller { name: this.documentNameValue, token: this.oauthTokenValue, document: ydoc, + onAuthenticationFailed: () => { + console.warn('[InitYjsProvider] Authentication failed'); + }, }); LiveCollaborationManager.initializeYjsProvider(provider, ydoc); + + if (this.refreshUrlValue && this.tokenExpiresInSecondsValue) { + this.tokenRefreshService = new TokenRefreshService(provider, this.refreshUrlValue); + this.tokenRefreshService.scheduleRefresh(this.tokenExpiresInSecondsValue); + } } disconnect():void { + this.tokenRefreshService?.destroy(); + this.tokenRefreshService = null; LiveCollaborationManager.destroy(); } } diff --git a/frontend/src/stimulus/services/documents/token-refresh.service.ts b/frontend/src/stimulus/services/documents/token-refresh.service.ts new file mode 100644 index 00000000000..d5d638147ec --- /dev/null +++ b/frontend/src/stimulus/services/documents/token-refresh.service.ts @@ -0,0 +1,145 @@ +/* + * -- 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. + * ++ + */ + +import type { HocuspocusProvider } from '@hocuspocus/provider'; +import { getMetaContent } from 'core-app/core/setup/globals/global-helpers'; + +interface TokenResponse { + encrypted_token:string; + expires_at:string; + expires_in_seconds:number; +} + +const REFRESH_THRESHOLD = 0.8; // 80% of the token lifetime +const RETRY_DELAY_MS = 5000; // 5 seconds + +/** + * Manages OAuth token refresh for Hocuspocus collaborative editing sessions. + * + * Proactively refreshes tokens at 80% of lifetime using session auth, + * then sends new token to Hocuspocus server via stateless channel. + * + * ``` + * Client OpenProject Hocuspocus + * │ [80% of token TTL] │ │ + * │── POST /documents/{id}/oauth/refresh_token ──►│ │ + * │◄─────────────── {encrypted_token} ───│ │ + * │── REFRESH: ───────────────────┼──────────────────────────────────►│ updates context.token + * │ [schedule next refresh] │ │ + * ``` + */ +export class TokenRefreshService { + private refreshTimer:ReturnType | null = null; + private provider:HocuspocusProvider; + private refreshUrl:string; + private destroyed = false; + + constructor(provider:HocuspocusProvider, refreshUrl:string) { + this.provider = provider; + this.refreshUrl = refreshUrl; + } + + scheduleRefresh(expiresInSeconds:number):void { + this.clearTimer(); + + if (this.destroyed) { + return; + } + + const refreshDelayMs = Math.floor(expiresInSeconds * REFRESH_THRESHOLD * 1000); + + this.refreshTimer = setTimeout(() => { + void this.performRefresh(); + }, refreshDelayMs); + } + + async performRefresh():Promise { + if (this.destroyed) { + return; + } + + try { + const response = await fetch(this.refreshUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-Token': getMetaContent('csrf-token'), + 'X-Authentication-Scheme': 'Session', + }, + credentials: 'same-origin', + }); + + if (response.status === 401 || response.status === 403) { + console.warn('[TokenRefresh] Session expired, stopping refresh'); + return; + } + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const data = await response.json() as TokenResponse; + + this.sendTokenToServer(data.encrypted_token); + this.scheduleRefresh(data.expires_in_seconds); + } catch (error) { + console.error('[TokenRefresh] Refresh failed, retrying...', error); + this.scheduleRetry(); + } + } + + destroy():void { + this.destroyed = true; + this.clearTimer(); + } + + private sendTokenToServer(encryptedToken:string):void { + this.provider.sendStateless(`REFRESH:${encryptedToken}`); + } + + private scheduleRetry():void { + this.clearTimer(); + + if (this.destroyed) { + return; + } + + this.refreshTimer = setTimeout(() => { + void this.performRefresh(); + }, RETRY_DELAY_MS); + } + + private clearTimer():void { + if (this.refreshTimer !== null) { + clearTimeout(this.refreshTimer); + this.refreshTimer = null; + } + } +} diff --git a/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.html.erb b/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.html.erb index ae689169fa8..163a584732a 100644 --- a/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.html.erb +++ b/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.html.erb @@ -34,7 +34,10 @@ "documents--init-yjs-provider", "documents--init-yjs-provider-hocuspocus-url-value": Setting.collaborative_editing_hocuspocus_url, "documents--init-yjs-provider-oauth-token-value": oauth_token, - "documents--init-yjs-provider-document-name-value": resource_url + "documents--init-yjs-provider-document-name-value": resource_url, + "documents--init-yjs-provider-token-expires-at-value": token_expires_at, + "documents--init-yjs-provider-token-expires-in-seconds-value": token_expires_in_seconds, + "documents--init-yjs-provider-refresh-url-value": refresh_token_url ) %> diff --git a/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.rb b/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.rb index 3197633bab9..aadbe1a72e9 100644 --- a/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.rb +++ b/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.rb @@ -36,7 +36,7 @@ module Documents alias_method :document, :model - options :project, :oauth_token, :state, :readonly + options :project, :oauth_token, :token_expires_at, :token_expires_in_seconds, :state, :readonly private @@ -46,6 +46,10 @@ module Documents API::V3::Utilities::PathHelper::ApiV3Path.document(document.id) ).to_s end + + def refresh_token_url + document_oauth_refresh_token_path(document) + end end end end diff --git a/modules/documents/app/controllers/documents/oauth/refresh_tokens_controller.rb b/modules/documents/app/controllers/documents/oauth/refresh_tokens_controller.rb new file mode 100644 index 00000000000..ec3a5df1eeb --- /dev/null +++ b/modules/documents/app/controllers/documents/oauth/refresh_tokens_controller.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module Documents + module OAuth + class RefreshTokensController < ApplicationController + model_object Document + + before_action :find_model_object + before_action :find_project_from_association + before_action :authorize + + def create + token_result = TokenWithMetadataService.new(user: current_user).call + + if token_result.success? + render json: token_result.result, status: :ok + else + render json: { error: token_result.message }, status: :unprocessable_entity + end + end + + private + + def find_model_object(object_id = :document_id) = super + end + end +end diff --git a/modules/documents/app/controllers/documents_controller.rb b/modules/documents/app/controllers/documents_controller.rb index 18361879643..c6479280aa7 100644 --- a/modules/documents/app/controllers/documents_controller.rb +++ b/modules/documents/app/controllers/documents_controller.rb @@ -222,33 +222,22 @@ class DocumentsController < ApplicationController redirect_to document_path(call.result, state: :edit) end - # rubocop:disable Metrics/AbcSize def generate_encrypted_oauth_token - if !current_user.allowed_in_project?(:view_documents, @project) - return - end + return unless current_user.allowed_in_project?(:view_documents, @project) - result = Documents::OAuth::GenerateTokenService + token_result = Documents::OAuth::TokenWithMetadataService .new(user: current_user) .call - if result.failure? - Rails.logger.error("Failed to generate OAuth token for document #{@document.id}: #{result.errors}") + if token_result.failure? + Rails.logger.error("Failed to generate OAuth token for document #{@document.id}: #{token_result.errors}") return end - result = Documents::OAuth::EncryptTokenService - .new(token: result.result.plaintext_token) - .call - - if result.failure? - Rails.logger.error("Failed to encrypt OAuth token for document #{@document.id}: #{result.errors}") - return - end - - @oauth_token = result.result + @oauth_token = token_result.result[:encrypted_token] + @token_expires_at = token_result.result[:expires_at] + @token_expires_in_seconds = token_result.result[:expires_in_seconds] end - # rubocop:enable Metrics/AbcSize def update_header_component_via_turbo_stream(state: :show) update_via_turbo_stream( diff --git a/modules/documents/app/services/documents/oauth/encrypt_token_service.rb b/modules/documents/app/services/documents/oauth/encrypt_token_service.rb index b7c64a9ed32..1490220ca3d 100644 --- a/modules/documents/app/services/documents/oauth/encrypt_token_service.rb +++ b/modules/documents/app/services/documents/oauth/encrypt_token_service.rb @@ -49,7 +49,7 @@ module Documents ServiceResult.success(result: encrypted) rescue StandardError => e - ServiceResult.failure(errors: e.message) + ServiceResult.failure(errors: e) end private diff --git a/modules/documents/app/services/documents/oauth/generate_token_service.rb b/modules/documents/app/services/documents/oauth/generate_token_service.rb index 63304a0b250..74a720f9754 100644 --- a/modules/documents/app/services/documents/oauth/generate_token_service.rb +++ b/modules/documents/app/services/documents/oauth/generate_token_service.rb @@ -58,7 +58,7 @@ module Documents application.access_tokens.create( resource_owner_id: @user.id, scopes: "api_v3", - expires_in: 24.hours.to_i + expires_in: 5.minutes.to_i ) end end diff --git a/modules/documents/app/services/documents/oauth/token_with_metadata_service.rb b/modules/documents/app/services/documents/oauth/token_with_metadata_service.rb new file mode 100644 index 00000000000..f64f8df8fec --- /dev/null +++ b/modules/documents/app/services/documents/oauth/token_with_metadata_service.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module Documents + module OAuth + class TokenWithMetadataService < BaseServices::BaseCallable + def initialize(user:) + super() + + @user = user + end + + def perform + token_result = GenerateTokenService.new(user: @user).call + return token_result unless token_result.success? + + access_token = token_result.result + encrypt_result = EncryptTokenService.new(token: access_token.plaintext_token).call + return encrypt_result unless encrypt_result.success? + + ServiceResult.success( + result: { + encrypted_token: encrypt_result.result, + expires_at: access_token.expires_in.seconds.from_now.iso8601, + expires_in_seconds: access_token.expires_in + } + ) + end + end + end +end diff --git a/modules/documents/app/views/documents/show.html.erb b/modules/documents/app/views/documents/show.html.erb index 182f207e846..1a9239d95a5 100644 --- a/modules/documents/app/views/documents/show.html.erb +++ b/modules/documents/app/views/documents/show.html.erb @@ -36,7 +36,13 @@ <%= render( Documents::ShowEditView::BlockNoteEditorComponent.new( - @document, project: @project, oauth_token: @oauth_token, state: @state, readonly: @readonly + @document, + project: @project, + oauth_token: @oauth_token, + token_expires_at: @token_expires_at, + token_expires_in_seconds: @token_expires_in_seconds, + state: @state, + readonly: @readonly ) ) %> diff --git a/modules/documents/config/routes.rb b/modules/documents/config/routes.rb index ecfba244008..047b028d16a 100644 --- a/modules/documents/config/routes.rb +++ b/modules/documents/config/routes.rb @@ -50,6 +50,10 @@ Rails.application.routes.draw do get :render_connection_error, defaults: { format: :turbo_stream } get :render_connection_recovery, defaults: { format: :turbo_stream } end + + namespace :oauth do + resource :refresh_token, only: [:create], controller: "/documents/oauth/refresh_tokens", defaults: { format: :json } + end end scope module: :documents do diff --git a/modules/documents/lib/open_project/documents/engine.rb b/modules/documents/lib/open_project/documents/engine.rb index 10326d3ccb1..c43d5916b25 100644 --- a/modules/documents/lib/open_project/documents/engine.rb +++ b/modules/documents/lib/open_project/documents/engine.rb @@ -59,7 +59,8 @@ module OpenProject::Documents render_avatars render_last_saved_at render_connection_error render_connection_recovery ], - "documents/menus": %i[show] + "documents/menus": %i[show], + "documents/oauth/refresh_tokens": %i[create] }, permissible_on: :project permission :manage_documents, diff --git a/modules/documents/spec/controllers/documents/oauth/refresh_tokens_controller_spec.rb b/modules/documents/spec/controllers/documents/oauth/refresh_tokens_controller_spec.rb new file mode 100644 index 00000000000..876d7fa01c9 --- /dev/null +++ b/modules/documents/spec/controllers/documents/oauth/refresh_tokens_controller_spec.rb @@ -0,0 +1,126 @@ +# 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 Documents::OAuth::RefreshTokensController do + let(:project) { create(:project) } + let(:document) { create(:document, project:) } + let(:user) { create(:user) } + let(:role) { create(:project_role, permissions: [:view_documents]) } + + before do + allow(Setting) + .to receive(:collaborative_editing_hocuspocus_secret) + .and_return("test_secret_for_encryption") + end + + describe "POST #create" do + context "when user is not logged in" do + it "redirects to login" do + post :create, params: { document_id: document.id } + + expect(response).to redirect_to(signin_path(back_url: document_oauth_refresh_token_url(document))) + end + end + + context "when user is logged in but lacks permission" do + before do + login_as(user) + end + + it "returns forbidden" do + post :create, params: { document_id: document.id } + + expect(response).to have_http_status(:forbidden) + end + end + + context "when user has view_documents permission" do + before do + login_as(user) + create(:member, project:, user:, roles: [role]) + end + + it "returns a successful JSON response" do + expect do + post :create, params: { document_id: document.id } + end.to change(Doorkeeper::AccessToken, :count).by(1) + + expect(response).to have_http_status(:ok) + expect(response.content_type).to include("application/json") + + json = response.parsed_body + + aggregate_failures "returns token metadata" do + expect(json).to have_key("encrypted_token") + expect(json).to have_key("expires_at") + expect(json).to have_key("expires_in_seconds") + end + + aggregate_failures "valid expiration values" do + expect { Time.iso8601(json["expires_at"]) }.not_to raise_error + expect(json["expires_in_seconds"]).to eq(5.minutes.to_i) + end + end + end + + context "when document does not exist" do + before do + login_as(user) + end + + it "returns not found" do + post :create, params: { document_id: 999_999 } + + expect(response).to have_http_status(:not_found) + end + end + + context "when token generation fails" do + before do + login_as(user) + create(:member, project:, user:, roles: [role]) + + allow(Setting) + .to receive(:collaborative_editing_hocuspocus_secret) + .and_return(nil) + end + + it "returns unprocessable entity" do + post :create, params: { document_id: document.id } + + expect(response).to have_http_status(:unprocessable_entity) + json = response.parsed_body + expect(json).to have_key("error") + end + end + end +end diff --git a/modules/documents/spec/services/documents/oauth/token_with_metadata_service_spec.rb b/modules/documents/spec/services/documents/oauth/token_with_metadata_service_spec.rb new file mode 100644 index 00000000000..43444f4c062 --- /dev/null +++ b/modules/documents/spec/services/documents/oauth/token_with_metadata_service_spec.rb @@ -0,0 +1,92 @@ +# 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 Documents::OAuth::TokenWithMetadataService, + with_settings: { collaborative_editing_hocuspocus_secret: "test_secret_for_encryption" } do + subject(:service_call) { described_class.new(user:).call } + + let(:user) { create(:user) } + + describe "#call" do + it "returns a successful service result" do + expect(service_call).to be_success + end + + it "returns an encrypted token" do + result = service_call.result + + expect(result[:encrypted_token]).to be_a(String) + expect(result[:encrypted_token]).not_to be_empty + end + + it "returns expires_at as ISO8601 timestamp" do + result = service_call.result + + expect(result[:expires_at]).to be_a(String) + expect { Time.iso8601(result[:expires_at]) }.not_to raise_error + end + + it "returns expires_in_seconds matching the token expiry" do + result = service_call.result + + expect(result[:expires_in_seconds]).to eq(5.minutes.to_i) + end + + it "creates a new access token" do + expect { service_call }.to change(Doorkeeper::AccessToken, :count).by(1) + end + end + + context "when token generation fails" do + before do + allow_any_instance_of(Documents::OAuth::GenerateTokenService) # rubocop:disable RSpec/AnyInstance + .to receive(:call) + .and_return(ServiceResult.failure(errors: "Token generation failed")) + end + + it "returns a failure" do + expect(service_call).to be_failure + end + end + + context "when encryption fails" do + before do + allow(Setting) + .to receive(:collaborative_editing_hocuspocus_secret) + .and_return(nil) + end + + it "returns a failure" do + expect(service_call).to be_failure + end + end +end From cb91611a0d7ac9ec54de73e3d23694e519c5f9cd Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Tue, 20 Jan 2026 21:41:39 +0300 Subject: [PATCH 002/293] Fix stale token on Hocuspocus reconnection Use async token function in HocuspocusProvider to fetch fresh token on reconnection instead of reusing the original static token. Extracts fetchToken to static method on TokenRefreshService for reuse. --- .../documents/init-yjs-provider.controller.ts | 17 ++++++- .../documents/token-refresh.service.ts | 49 +++++++++++-------- 2 files changed, 44 insertions(+), 22 deletions(-) diff --git a/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts b/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts index ef4dd52fd25..a5ce70bef9a 100644 --- a/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts @@ -53,13 +53,14 @@ export default class extends Controller { declare readonly refreshUrlValue:string; private tokenRefreshService:TokenRefreshService | null = null; + private isFirstConnection = true; connect():void { const ydoc:Doc = new Y.Doc(); const provider = new HocuspocusProvider({ url: this.hocuspocusUrlValue, name: this.documentNameValue, - token: this.oauthTokenValue, + token: async () => this.getToken(), document: ydoc, onAuthenticationFailed: () => { console.warn('[InitYjsProvider] Authentication failed'); @@ -74,6 +75,20 @@ export default class extends Controller { } } + private async getToken():Promise { + if (this.isFirstConnection) { + this.isFirstConnection = false; + return this.oauthTokenValue; + } + + return this.fetchFreshToken(); + } + + private async fetchFreshToken():Promise { + const data = await TokenRefreshService.fetchToken(this.refreshUrlValue); + return data.encrypted_token; + } + disconnect():void { this.tokenRefreshService?.destroy(); this.tokenRefreshService = null; diff --git a/frontend/src/stimulus/services/documents/token-refresh.service.ts b/frontend/src/stimulus/services/documents/token-refresh.service.ts index d5d638147ec..9c10a850232 100644 --- a/frontend/src/stimulus/services/documents/token-refresh.service.ts +++ b/frontend/src/stimulus/services/documents/token-refresh.service.ts @@ -31,7 +31,7 @@ import type { HocuspocusProvider } from '@hocuspocus/provider'; import { getMetaContent } from 'core-app/core/setup/globals/global-helpers'; -interface TokenResponse { +export interface TokenResponse { encrypted_token:string; expires_at:string; expires_in_seconds:number; @@ -80,36 +80,43 @@ export class TokenRefreshService { }, refreshDelayMs); } + static async fetchToken(refreshUrl:string):Promise { + const response = await fetch(refreshUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-Token': getMetaContent('csrf-token'), + 'X-Authentication-Scheme': 'Session', + }, + credentials: 'same-origin', + }); + + if (response.status === 401 || response.status === 403) { + throw new Error('Session expired'); + } + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + return response.json() as Promise; + } + async performRefresh():Promise { if (this.destroyed) { return; } try { - const response = await fetch(this.refreshUrl, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-CSRF-Token': getMetaContent('csrf-token'), - 'X-Authentication-Scheme': 'Session', - }, - credentials: 'same-origin', - }); - - if (response.status === 401 || response.status === 403) { - console.warn('[TokenRefresh] Session expired, stopping refresh'); - return; - } - - if (!response.ok) { - throw new Error(`HTTP ${response.status}: ${response.statusText}`); - } - - const data = await response.json() as TokenResponse; + const data = await TokenRefreshService.fetchToken(this.refreshUrl); this.sendTokenToServer(data.encrypted_token); this.scheduleRefresh(data.expires_in_seconds); } catch (error) { + if (error instanceof Error && error.message === 'Session expired') { + console.warn('[TokenRefresh] Session expired, stopping refresh'); + return; + } console.error('[TokenRefresh] Refresh failed, retrying...', error); this.scheduleRetry(); } From 4062c231bdd0e3e7a8ca6fdd96e5300a38ab3a18 Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Wed, 21 Jan 2026 07:18:26 +0300 Subject: [PATCH 003/293] Add retry logic and unified RefreshError for token refresh Up to 3 retries for 5xx/network errors, no retry for 4xx --- .../documents/token-refresh.service.ts | 50 +++++++++++++++---- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/frontend/src/stimulus/services/documents/token-refresh.service.ts b/frontend/src/stimulus/services/documents/token-refresh.service.ts index 9c10a850232..82497a57208 100644 --- a/frontend/src/stimulus/services/documents/token-refresh.service.ts +++ b/frontend/src/stimulus/services/documents/token-refresh.service.ts @@ -37,8 +37,28 @@ export interface TokenResponse { expires_in_seconds:number; } +export type RefreshErrorKind = 'session_expired' | 'http_error' | 'unknown'; + +export class RefreshError extends Error { + constructor( + public readonly kind:RefreshErrorKind, + message:string, + public readonly status?:number, + ) { + super(message); + this.name = 'RefreshError'; + } + + get isRetryable():boolean { + if (this.kind === 'session_expired') return false; + if (this.status !== undefined) return this.status >= 500 || this.status === 429; + return this.kind === 'unknown'; + } +} + const REFRESH_THRESHOLD = 0.8; // 80% of the token lifetime const RETRY_DELAY_MS = 5000; // 5 seconds +const MAX_RETRIES = 3; /** * Manages OAuth token refresh for Hocuspocus collaborative editing sessions. @@ -49,10 +69,18 @@ const RETRY_DELAY_MS = 5000; // 5 seconds * ``` * Client OpenProject Hocuspocus * │ [80% of token TTL] │ │ - * │── POST /documents/{id}/oauth/refresh_token ──►│ │ + * │── POST /refresh_token ─────────────────►│ │ + * │ │ │ + * │ [success] │ │ * │◄─────────────── {encrypted_token} ───│ │ * │── REFRESH: ───────────────────┼──────────────────────────────────►│ updates context.token * │ [schedule next refresh] │ │ + * │ │ │ + * │ [5xx error] retry up to 3x │ │ + * │── POST /refresh_token ─────────────────►│ │ + * │ │ │ + * │ [401/403] stop - session expired │ │ + * │ [4xx] stop - non-retryable │ │ * ``` */ export class TokenRefreshService { @@ -60,6 +88,7 @@ export class TokenRefreshService { private provider:HocuspocusProvider; private refreshUrl:string; private destroyed = false; + private retryCount = 0; constructor(provider:HocuspocusProvider, refreshUrl:string) { this.provider = provider; @@ -73,7 +102,7 @@ export class TokenRefreshService { return; } - const refreshDelayMs = Math.floor(expiresInSeconds * REFRESH_THRESHOLD * 1000); + const refreshDelayMs = Math.max(0, Math.floor(expiresInSeconds * REFRESH_THRESHOLD * 1000)); this.refreshTimer = setTimeout(() => { void this.performRefresh(); @@ -92,11 +121,11 @@ export class TokenRefreshService { }); if (response.status === 401 || response.status === 403) { - throw new Error('Session expired'); + throw new RefreshError('session_expired', 'Session expired', response.status); } if (!response.ok) { - throw new Error(`HTTP ${response.status}: ${response.statusText}`); + throw new RefreshError('http_error', `HTTP ${response.status}: ${response.statusText}`, response.status); } return response.json() as Promise; @@ -110,14 +139,17 @@ export class TokenRefreshService { try { const data = await TokenRefreshService.fetchToken(this.refreshUrl); + this.retryCount = 0; this.sendTokenToServer(data.encrypted_token); this.scheduleRefresh(data.expires_in_seconds); } catch (error) { - if (error instanceof Error && error.message === 'Session expired') { - console.warn('[TokenRefresh] Session expired, stopping refresh'); - return; - } - console.error('[TokenRefresh] Refresh failed, retrying...', error); + const refreshError = error instanceof RefreshError + ? error + : new RefreshError('unknown', error instanceof Error ? error.message : 'Unknown error'); + + if (!refreshError.isRetryable || this.retryCount >= MAX_RETRIES) return; + + this.retryCount += 1; this.scheduleRetry(); } } From b1e0601460727ce4f2df0934f912a0bfa729a5a0 Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Wed, 21 Jan 2026 07:35:13 +0300 Subject: [PATCH 004/293] Emit event on token refresh failure for UI error state --- frontend/src/react/hooks/useCollaboration.ts | 12 ++++++++++ .../documents/token-refresh.service.ts | 22 ++++++++++++++----- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/frontend/src/react/hooks/useCollaboration.ts b/frontend/src/react/hooks/useCollaboration.ts index 3a446ef9114..27c5fa947c9 100644 --- a/frontend/src/react/hooks/useCollaboration.ts +++ b/frontend/src/react/hooks/useCollaboration.ts @@ -30,6 +30,7 @@ import { HocuspocusProvider } from '@hocuspocus/provider'; import { debugLog } from 'core-app/shared/helpers/debug_output'; +import { TOKEN_REFRESH_FAILED_EVENT } from 'core-stimulus/services/documents/token-refresh.service'; import { useCallback, useEffect, useRef, useState } from 'react'; import * as Y from 'yjs'; @@ -141,6 +142,17 @@ export function useCollaboration( } }, [hasTimedOut]); + useEffect(() => { + const handleTokenRefreshFailed = (event:Event) => { + const customEvent = event as CustomEvent<{ kind:string; message:string }>; + debugLog(`(BlockNote Editor) Token refresh failed: ${customEvent.detail.kind} - ${customEvent.detail.message}`); + setConnectionError(true); + }; + + document.addEventListener(TOKEN_REFRESH_FAILED_EVENT, handleTokenRefreshFailed); + return () => document.removeEventListener(TOKEN_REFRESH_FAILED_EVENT, handleTokenRefreshFailed); + }, []); + return { isLoading, connectionError } as const; } diff --git a/frontend/src/stimulus/services/documents/token-refresh.service.ts b/frontend/src/stimulus/services/documents/token-refresh.service.ts index 82497a57208..cefbd9d21f4 100644 --- a/frontend/src/stimulus/services/documents/token-refresh.service.ts +++ b/frontend/src/stimulus/services/documents/token-refresh.service.ts @@ -57,8 +57,11 @@ export class RefreshError extends Error { } const REFRESH_THRESHOLD = 0.8; // 80% of the token lifetime -const RETRY_DELAY_MS = 5000; // 5 seconds +const RETRY_DELAY_MS = 5000; const MAX_RETRIES = 3; +const MIN_REFRESH_DELAY_MS = 1000; + +export const TOKEN_REFRESH_FAILED_EVENT = 'op:token-refresh-failed'; /** * Manages OAuth token refresh for Hocuspocus collaborative editing sessions. @@ -102,7 +105,7 @@ export class TokenRefreshService { return; } - const refreshDelayMs = Math.max(0, Math.floor(expiresInSeconds * REFRESH_THRESHOLD * 1000)); + const refreshDelayMs = Math.max(MIN_REFRESH_DELAY_MS, Math.floor(expiresInSeconds * REFRESH_THRESHOLD * 1000)); this.refreshTimer = setTimeout(() => { void this.performRefresh(); @@ -132,9 +135,7 @@ export class TokenRefreshService { } async performRefresh():Promise { - if (this.destroyed) { - return; - } + if (this.destroyed) return; try { const data = await TokenRefreshService.fetchToken(this.refreshUrl); @@ -147,7 +148,10 @@ export class TokenRefreshService { ? error : new RefreshError('unknown', error instanceof Error ? error.message : 'Unknown error'); - if (!refreshError.isRetryable || this.retryCount >= MAX_RETRIES) return; + if (!refreshError.isRetryable || this.retryCount >= MAX_RETRIES) { + this.emitFailureEvent(refreshError); + return; + } this.retryCount += 1; this.scheduleRetry(); @@ -163,6 +167,12 @@ export class TokenRefreshService { this.provider.sendStateless(`REFRESH:${encryptedToken}`); } + private emitFailureEvent(error:RefreshError):void { + document.dispatchEvent(new CustomEvent(TOKEN_REFRESH_FAILED_EVENT, { + detail: { kind: error.kind, message: error.message }, + })); + } + private scheduleRetry():void { this.clearTimer(); From 3c33b4d6cbdfeef3ba356987e395a13e5efa29e8 Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Wed, 21 Jan 2026 07:47:12 +0300 Subject: [PATCH 005/293] Reset retryCount in scheduleRefresh for fresh retry budget --- .../stimulus/services/documents/token-refresh.service.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/frontend/src/stimulus/services/documents/token-refresh.service.ts b/frontend/src/stimulus/services/documents/token-refresh.service.ts index cefbd9d21f4..64bf72425ec 100644 --- a/frontend/src/stimulus/services/documents/token-refresh.service.ts +++ b/frontend/src/stimulus/services/documents/token-refresh.service.ts @@ -100,6 +100,7 @@ export class TokenRefreshService { scheduleRefresh(expiresInSeconds:number):void { this.clearTimer(); + this.retryCount = 0; if (this.destroyed) { return; @@ -131,7 +132,12 @@ export class TokenRefreshService { throw new RefreshError('http_error', `HTTP ${response.status}: ${response.statusText}`, response.status); } - return response.json() as Promise; + try { + return await response.json() as TokenResponse; + } catch (error) { + const message = error instanceof Error ? error.message : 'Failed to parse token response JSON'; + throw new RefreshError('http_error', message, response.status); + } } async performRefresh():Promise { @@ -140,7 +146,6 @@ export class TokenRefreshService { try { const data = await TokenRefreshService.fetchToken(this.refreshUrl); - this.retryCount = 0; this.sendTokenToServer(data.encrypted_token); this.scheduleRefresh(data.expires_in_seconds); } catch (error) { From 63409a84f8aeb546dbc37c5acc4ebe17c993de19 Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Wed, 21 Jan 2026 13:00:50 +0300 Subject: [PATCH 006/293] Refactor token refresh to use callback and sendToken API Replace sendStateless with provider.sendToken() for token sync --- .../documents/init-yjs-provider.controller.ts | 39 +++++++++++-------- .../documents/token-refresh.service.ts | 23 ++++++----- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts b/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts index a5ce70bef9a..cb29bda2c86 100644 --- a/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts @@ -53,14 +53,29 @@ export default class extends Controller { declare readonly refreshUrlValue:string; private tokenRefreshService:TokenRefreshService | null = null; - private isFirstConnection = true; + private currentToken = ''; + private canUseCachedToken = true; + + // On initial load, the DOM token is fresh. On reconnection (e.g., after server restart), + // we must fetch a fresh token since the cached one may be expired. + private getToken = async ():Promise => { + if (this.canUseCachedToken) { + this.canUseCachedToken = false; + return this.currentToken; + } + const data = await TokenRefreshService.fetchToken(this.refreshUrlValue); + this.currentToken = data.encrypted_token; + return this.currentToken; + }; connect():void { + this.currentToken = this.oauthTokenValue; + const ydoc:Doc = new Y.Doc(); const provider = new HocuspocusProvider({ url: this.hocuspocusUrlValue, name: this.documentNameValue, - token: async () => this.getToken(), + token: this.getToken, document: ydoc, onAuthenticationFailed: () => { console.warn('[InitYjsProvider] Authentication failed'); @@ -70,25 +85,15 @@ export default class extends Controller { LiveCollaborationManager.initializeYjsProvider(provider, ydoc); if (this.refreshUrlValue && this.tokenExpiresInSecondsValue) { - this.tokenRefreshService = new TokenRefreshService(provider, this.refreshUrlValue); + this.tokenRefreshService = new TokenRefreshService( + provider, + this.refreshUrlValue, + (newToken) => { this.currentToken = newToken; }, + ); this.tokenRefreshService.scheduleRefresh(this.tokenExpiresInSecondsValue); } } - private async getToken():Promise { - if (this.isFirstConnection) { - this.isFirstConnection = false; - return this.oauthTokenValue; - } - - return this.fetchFreshToken(); - } - - private async fetchFreshToken():Promise { - const data = await TokenRefreshService.fetchToken(this.refreshUrlValue); - return data.encrypted_token; - } - disconnect():void { this.tokenRefreshService?.destroy(); this.tokenRefreshService = null; diff --git a/frontend/src/stimulus/services/documents/token-refresh.service.ts b/frontend/src/stimulus/services/documents/token-refresh.service.ts index 64bf72425ec..1b4aa570ccc 100644 --- a/frontend/src/stimulus/services/documents/token-refresh.service.ts +++ b/frontend/src/stimulus/services/documents/token-refresh.service.ts @@ -67,20 +67,20 @@ export const TOKEN_REFRESH_FAILED_EVENT = 'op:token-refresh-failed'; * Manages OAuth token refresh for Hocuspocus collaborative editing sessions. * * Proactively refreshes tokens at 80% of lifetime using session auth, - * then sends new token to Hocuspocus server via stateless channel. + * then syncs new token to Hocuspocus server via built-in onTokenSync hook. * * ``` * Client OpenProject Hocuspocus * │ [80% of token TTL] │ │ - * │── POST /refresh_token ─────────────────►│ │ + * │── POST /refresh_token ──────────────►│ │ * │ │ │ * │ [success] │ │ * │◄─────────────── {encrypted_token} ───│ │ - * │── REFRESH: ───────────────────┼──────────────────────────────────►│ updates context.token + * │── sendToken() ───────────────────────┼──────────────────────────────────►│ onTokenSync updates context * │ [schedule next refresh] │ │ * │ │ │ * │ [5xx error] retry up to 3x │ │ - * │── POST /refresh_token ─────────────────►│ │ + * │── POST /refresh_token ──────────────►│ │ * │ │ │ * │ [401/403] stop - session expired │ │ * │ [4xx] stop - non-retryable │ │ @@ -90,12 +90,18 @@ export class TokenRefreshService { private refreshTimer:ReturnType | null = null; private provider:HocuspocusProvider; private refreshUrl:string; + private onTokenRefreshed:(token:string) => void; private destroyed = false; private retryCount = 0; - constructor(provider:HocuspocusProvider, refreshUrl:string) { + constructor( + provider:HocuspocusProvider, + refreshUrl:string, + onTokenRefreshed:(token:string) => void, + ) { this.provider = provider; this.refreshUrl = refreshUrl; + this.onTokenRefreshed = onTokenRefreshed; } scheduleRefresh(expiresInSeconds:number):void { @@ -146,7 +152,8 @@ export class TokenRefreshService { try { const data = await TokenRefreshService.fetchToken(this.refreshUrl); - this.sendTokenToServer(data.encrypted_token); + this.onTokenRefreshed(data.encrypted_token); + void this.provider.sendToken(); this.scheduleRefresh(data.expires_in_seconds); } catch (error) { const refreshError = error instanceof RefreshError @@ -168,10 +175,6 @@ export class TokenRefreshService { this.clearTimer(); } - private sendTokenToServer(encryptedToken:string):void { - this.provider.sendStateless(`REFRESH:${encryptedToken}`); - } - private emitFailureEvent(error:RefreshError):void { document.dispatchEvent(new CustomEvent(TOKEN_REFRESH_FAILED_EVENT, { detail: { kind: error.kind, message: error.message }, From 568125d50bdaf24a5b5959a05c9f880f6d7b29f1 Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Wed, 21 Jan 2026 14:57:29 +0300 Subject: [PATCH 007/293] Introduce handling for provider auth token issue or refresh failures In case of (first time) authentication failure or token refresh errors, signal to the blocknote container to transition to connection error state. --- frontend/src/react/hooks/useCollaboration.ts | 12 ++++++------ .../documents/init-yjs-provider.controller.ts | 6 ++++-- .../services/documents/token-refresh.service.ts | 7 ++++--- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/frontend/src/react/hooks/useCollaboration.ts b/frontend/src/react/hooks/useCollaboration.ts index 27c5fa947c9..6a04bd9edfe 100644 --- a/frontend/src/react/hooks/useCollaboration.ts +++ b/frontend/src/react/hooks/useCollaboration.ts @@ -30,7 +30,7 @@ import { HocuspocusProvider } from '@hocuspocus/provider'; import { debugLog } from 'core-app/shared/helpers/debug_output'; -import { TOKEN_REFRESH_FAILED_EVENT } from 'core-stimulus/services/documents/token-refresh.service'; +import { PROVIDER_AUTH_ERROR_EVENT, ProviderAuthErrorKind } from 'core-stimulus/services/documents/token-refresh.service'; import { useCallback, useEffect, useRef, useState } from 'react'; import * as Y from 'yjs'; @@ -143,14 +143,14 @@ export function useCollaboration( }, [hasTimedOut]); useEffect(() => { - const handleTokenRefreshFailed = (event:Event) => { - const customEvent = event as CustomEvent<{ kind:string; message:string }>; - debugLog(`(BlockNote Editor) Token refresh failed: ${customEvent.detail.kind} - ${customEvent.detail.message}`); + const handleProviderAuthError = (event:Event) => { + const customEvent = event as CustomEvent<{ kind:ProviderAuthErrorKind; message:string }>; + debugLog(`(BlockNote Editor) Provider auth error: ${customEvent.detail.kind} - ${customEvent.detail.message}`); setConnectionError(true); }; - document.addEventListener(TOKEN_REFRESH_FAILED_EVENT, handleTokenRefreshFailed); - return () => document.removeEventListener(TOKEN_REFRESH_FAILED_EVENT, handleTokenRefreshFailed); + document.addEventListener(PROVIDER_AUTH_ERROR_EVENT, handleProviderAuthError); + return () => document.removeEventListener(PROVIDER_AUTH_ERROR_EVENT, handleProviderAuthError); }, []); return { isLoading, connectionError } as const; diff --git a/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts b/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts index cb29bda2c86..864aaeef584 100644 --- a/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts @@ -31,7 +31,7 @@ import { HocuspocusProvider } from '@hocuspocus/provider'; import { Controller } from '@hotwired/stimulus'; import { LiveCollaborationManager } from 'core-stimulus/helpers/live-collaboration-helpers'; -import { TokenRefreshService } from 'core-stimulus/services/documents/token-refresh.service'; +import { PROVIDER_AUTH_ERROR_EVENT, ProviderAuthErrorKind, TokenRefreshService } from 'core-stimulus/services/documents/token-refresh.service'; import type { Doc } from 'yjs'; import * as Y from 'yjs'; @@ -78,7 +78,9 @@ export default class extends Controller { token: this.getToken, document: ydoc, onAuthenticationFailed: () => { - console.warn('[InitYjsProvider] Authentication failed'); + document.dispatchEvent(new CustomEvent(PROVIDER_AUTH_ERROR_EVENT, { + detail: { kind: 'authentication' as ProviderAuthErrorKind, message: 'Authentication failed' }, + })); }, }); diff --git a/frontend/src/stimulus/services/documents/token-refresh.service.ts b/frontend/src/stimulus/services/documents/token-refresh.service.ts index 1b4aa570ccc..fe71e31b5be 100644 --- a/frontend/src/stimulus/services/documents/token-refresh.service.ts +++ b/frontend/src/stimulus/services/documents/token-refresh.service.ts @@ -61,7 +61,8 @@ const RETRY_DELAY_MS = 5000; const MAX_RETRIES = 3; const MIN_REFRESH_DELAY_MS = 1000; -export const TOKEN_REFRESH_FAILED_EVENT = 'op:token-refresh-failed'; +export type ProviderAuthErrorKind = 'token_refresh' | 'authentication'; +export const PROVIDER_AUTH_ERROR_EVENT = 'op:provider-auth-error'; /** * Manages OAuth token refresh for Hocuspocus collaborative editing sessions. @@ -176,8 +177,8 @@ export class TokenRefreshService { } private emitFailureEvent(error:RefreshError):void { - document.dispatchEvent(new CustomEvent(TOKEN_REFRESH_FAILED_EVENT, { - detail: { kind: error.kind, message: error.message }, + document.dispatchEvent(new CustomEvent(PROVIDER_AUTH_ERROR_EVENT, { + detail: { kind: 'token_refresh' as ProviderAuthErrorKind, message: error.message }, })); } From 08f0c4c8e91205c7998e37495853cd98a29d84a9 Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Wed, 21 Jan 2026 19:26:28 +0300 Subject: [PATCH 008/293] Merge in packaged token payload --- .../documents/init-yjs-provider.controller.ts | 6 +-- .../block_note_editor_component.html.erb | 6 +-- .../block_note_editor_component.rb | 9 +--- .../oauth/refresh_tokens_controller.rb | 6 ++- .../app/controllers/documents_controller.rb | 18 +++---- .../forms/documents/block_note_editor_form.rb | 6 +-- .../oauth/token_with_metadata_service.rb | 31 ++++++++++- .../app/views/documents/show.html.erb | 3 +- .../controllers/documents_controller_spec.rb | 10 ++-- .../oauth/token_with_metadata_service_spec.rb | 52 +++++++++++++++++-- 10 files changed, 107 insertions(+), 40 deletions(-) diff --git a/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts b/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts index 864aaeef584..ee7b99699b3 100644 --- a/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts @@ -38,7 +38,7 @@ import * as Y from 'yjs'; export default class extends Controller { static values = { hocuspocusUrl: String, - oauthToken: String, + tokenPayload: String, documentName: String, tokenExpiresAt: String, tokenExpiresInSeconds: Number, @@ -46,7 +46,7 @@ export default class extends Controller { }; declare readonly hocuspocusUrlValue:string; - declare readonly oauthTokenValue:string; + declare readonly tokenPayloadValue:string; declare readonly documentNameValue:string; declare readonly tokenExpiresAtValue:string; declare readonly tokenExpiresInSecondsValue:number; @@ -69,7 +69,7 @@ export default class extends Controller { }; connect():void { - this.currentToken = this.oauthTokenValue; + this.currentToken = this.tokenPayloadValue; const ydoc:Doc = new Y.Doc(); const provider = new HocuspocusProvider({ diff --git a/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.html.erb b/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.html.erb index 163a584732a..480f65be8ab 100644 --- a/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.html.erb +++ b/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.html.erb @@ -33,7 +33,7 @@ helpers.content_controller( "documents--init-yjs-provider", "documents--init-yjs-provider-hocuspocus-url-value": Setting.collaborative_editing_hocuspocus_url, - "documents--init-yjs-provider-oauth-token-value": oauth_token, + "documents--init-yjs-provider-token-payload-value": token_payload, "documents--init-yjs-provider-document-name-value": resource_url, "documents--init-yjs-provider-token-expires-at-value": token_expires_at, "documents--init-yjs-provider-token-expires-in-seconds-value": token_expires_in_seconds, @@ -46,7 +46,7 @@ component.with_main(classes: "op-document-view--main") do flex_layout(align_items: :center) do |flex| flex.with_row(classes: "op-document-view--header") do - render Documents::ShowEditView::PageHeaderComponent.new(document, project:, state:, readonly:) + render Documents::ShowEditView::PageHeaderComponent.new(document, project:, state:) end flex.with_row(classes: "op-document-view--editor") do @@ -56,7 +56,7 @@ method: :patch, data: { turbo: false } ) do |form| - render Documents::BlockNoteEditorForm.new(form, oauth_token:, readonly:) + render Documents::BlockNoteEditorForm.new(form, token_payload:, readonly:) end end end diff --git a/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.rb b/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.rb index aadbe1a72e9..df0de565812 100644 --- a/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.rb +++ b/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.rb @@ -36,17 +36,10 @@ module Documents alias_method :document, :model - options :project, :oauth_token, :token_expires_at, :token_expires_in_seconds, :state, :readonly + options :project, :token_payload, :resource_url, :token_expires_at, :token_expires_in_seconds, :state, :readonly private - def resource_url - URI.join( - root_url, - API::V3::Utilities::PathHelper::ApiV3Path.document(document.id) - ).to_s - end - def refresh_token_url document_oauth_refresh_token_path(document) end diff --git a/modules/documents/app/controllers/documents/oauth/refresh_tokens_controller.rb b/modules/documents/app/controllers/documents/oauth/refresh_tokens_controller.rb index ec3a5df1eeb..408a994be97 100644 --- a/modules/documents/app/controllers/documents/oauth/refresh_tokens_controller.rb +++ b/modules/documents/app/controllers/documents/oauth/refresh_tokens_controller.rb @@ -38,7 +38,11 @@ module Documents before_action :authorize def create - token_result = TokenWithMetadataService.new(user: current_user).call + token_result = TokenWithMetadataService.new( + user: current_user, + document: @document, + project: @project + ).call if token_result.success? render json: token_result.result, status: :ok diff --git a/modules/documents/app/controllers/documents_controller.rb b/modules/documents/app/controllers/documents_controller.rb index c6479280aa7..5a14a90522f 100644 --- a/modules/documents/app/controllers/documents_controller.rb +++ b/modules/documents/app/controllers/documents_controller.rb @@ -61,8 +61,7 @@ class DocumentsController < ApplicationController @attachments = @document.attachments.order(Arel.sql("created_at DESC")) if @document.collaborative? && Setting.real_time_text_collaboration_enabled? - generate_encrypted_oauth_token - derive_readonly_from_permissions + setup_collaboration_context derive_show_edit_state_from_params end end @@ -222,19 +221,21 @@ class DocumentsController < ApplicationController redirect_to document_path(call.result, state: :edit) end - def generate_encrypted_oauth_token + def setup_collaboration_context # rubocop:disable Metrics/AbcSize return unless current_user.allowed_in_project?(:view_documents, @project) token_result = Documents::OAuth::TokenWithMetadataService - .new(user: current_user) + .new(user: current_user, document: @document, project: @project) .call if token_result.failure? - Rails.logger.error("Failed to generate OAuth token for document #{@document.id}: #{token_result.errors}") + Rails.logger.error("Failed to generate token payload for document #{@document.id}: #{token_result.errors}") return end - @oauth_token = token_result.result[:encrypted_token] + @token_payload = token_result.result[:encrypted_token] + @resource_url = token_result.result[:resource_url] + @readonly = token_result.result[:readonly] @token_expires_at = token_result.result[:expires_at] @token_expires_in_seconds = token_result.result[:expires_in_seconds] end @@ -248,9 +249,4 @@ class DocumentsController < ApplicationController def derive_show_edit_state_from_params @state = params[:state] == "edit" ? :edit : :show end - - def derive_readonly_from_permissions - @readonly = current_user.allowed_in_project?(:view_documents, @project) && - !current_user.allowed_in_project?(:manage_documents, @project) - end end diff --git a/modules/documents/app/forms/documents/block_note_editor_form.rb b/modules/documents/app/forms/documents/block_note_editor_form.rb index 4ca463e5e76..e82101fdaad 100644 --- a/modules/documents/app/forms/documents/block_note_editor_form.rb +++ b/modules/documents/app/forms/documents/block_note_editor_form.rb @@ -42,11 +42,11 @@ module Documents ) end - attr_reader :oauth_token, :readonly + attr_reader :token_payload, :readonly - def initialize(oauth_token: nil, readonly: false) + def initialize(token_payload: nil, readonly: false) super() - @oauth_token = oauth_token + @token_payload = token_payload @readonly = readonly end diff --git a/modules/documents/app/services/documents/oauth/token_with_metadata_service.rb b/modules/documents/app/services/documents/oauth/token_with_metadata_service.rb index f64f8df8fec..52606e7375e 100644 --- a/modules/documents/app/services/documents/oauth/token_with_metadata_service.rb +++ b/modules/documents/app/services/documents/oauth/token_with_metadata_service.rb @@ -31,10 +31,14 @@ module Documents module OAuth class TokenWithMetadataService < BaseServices::BaseCallable - def initialize(user:) + include API::V3::Utilities::PathHelper + + def initialize(user:, document:, project:) super() @user = user + @document = document + @project = project end def perform @@ -42,17 +46,40 @@ module Documents return token_result unless token_result.success? access_token = token_result.result - encrypt_result = EncryptTokenService.new(token: access_token.plaintext_token).call + + payload = { + resource_url:, + oauth_token: access_token.plaintext_token, + readonly: + } + + encrypt_result = EncryptTokenService.new(token: payload.to_json).call return encrypt_result unless encrypt_result.success? ServiceResult.success( result: { encrypted_token: encrypt_result.result, + resource_url:, + readonly:, expires_at: access_token.expires_in.seconds.from_now.iso8601, expires_in_seconds: access_token.expires_in } ) end + + private + + def resource_url + @resource_url ||= URI.join( + OpenProject::StaticRouting::StaticUrlHelpers.new.root_url, + api_v3_paths.document(@document.id) + ).to_s + end + + def readonly + @readonly ||= @user.allowed_in_project?(:view_documents, @project) && + !@user.allowed_in_project?(:manage_documents, @project) + end end end end diff --git a/modules/documents/app/views/documents/show.html.erb b/modules/documents/app/views/documents/show.html.erb index 1a9239d95a5..f0daa4be93c 100644 --- a/modules/documents/app/views/documents/show.html.erb +++ b/modules/documents/app/views/documents/show.html.erb @@ -38,7 +38,8 @@ Documents::ShowEditView::BlockNoteEditorComponent.new( @document, project: @project, - oauth_token: @oauth_token, + token_payload: @token_payload, + resource_url: @resource_url, token_expires_at: @token_expires_at, token_expires_in_seconds: @token_expires_in_seconds, state: @state, diff --git a/modules/documents/spec/controllers/documents_controller_spec.rb b/modules/documents/spec/controllers/documents_controller_spec.rb index ba1ee55edcd..d2428fe99b6 100644 --- a/modules/documents/spec/controllers/documents_controller_spec.rb +++ b/modules/documents/spec/controllers/documents_controller_spec.rb @@ -186,7 +186,7 @@ RSpec.describe DocumentsController do end end - describe "generate_oauth_token", + describe "setup_collaboration_context", with_config: { collaborative_editing_hocuspocus_url: "wss://hocuspocus.local", collaborative_editing_hocuspocus_secret: "secret1234" @@ -206,18 +206,18 @@ RSpec.describe DocumentsController do context "when user has manage_documents permission" do current_user { user_with_manage } - it "generates an OAuth token for show action" do + it "generates a token payload for show action" do get :show, params: { id: document.id } - expect(assigns(:oauth_token)).to be_present + expect(assigns(:token_payload)).to be_present end end context "when user does not have manage_documents permission" do current_user { user_without_manage } - it "generates an OAuth token for show action" do + it "generates a token payload for show action" do get :show, params: { id: document.id } - expect(assigns(:oauth_token)).to be_present + expect(assigns(:token_payload)).to be_present end end end diff --git a/modules/documents/spec/services/documents/oauth/token_with_metadata_service_spec.rb b/modules/documents/spec/services/documents/oauth/token_with_metadata_service_spec.rb index 43444f4c062..d8aaca28087 100644 --- a/modules/documents/spec/services/documents/oauth/token_with_metadata_service_spec.rb +++ b/modules/documents/spec/services/documents/oauth/token_with_metadata_service_spec.rb @@ -32,20 +32,54 @@ require "spec_helper" RSpec.describe Documents::OAuth::TokenWithMetadataService, with_settings: { collaborative_editing_hocuspocus_secret: "test_secret_for_encryption" } do - subject(:service_call) { described_class.new(user:).call } + subject(:service_call) { described_class.new(user:, document:, project:).call } - let(:user) { create(:user) } + let(:project) { create(:project) } + let(:document) { create(:document, project:) } + let(:manage_role) { create(:project_role, permissions: %i[view_documents manage_documents]) } + let(:view_only_role) { create(:project_role, permissions: [:view_documents]) } + let(:user) { create(:user, member_with_roles: { project => manage_role }) } + + def decrypt_token(encrypted_token) + key = Digest::SHA256.digest("test_secret_for_encryption") + encryptor = ActiveSupport::MessageEncryptor.new( + key, + cipher: "aes-256-gcm", + serializer: ActiveSupport::MessageEncryptor::NullSerializer + ) + encryptor.decrypt_and_verify(encrypted_token) + end describe "#call" do it "returns a successful service result" do expect(service_call).to be_success end - it "returns an encrypted token" do + it "returns an encrypted token containing packed params" do result = service_call.result expect(result[:encrypted_token]).to be_a(String) expect(result[:encrypted_token]).not_to be_empty + + # Verify the encrypted token contains packed params by decrypting + decrypted = decrypt_token(result[:encrypted_token]) + payload = JSON.parse(decrypted) + + expect(payload["resource_url"]).to include("/api/v3/documents/#{document.id}") + expect(payload["oauth_token"]).to be_present + expect(payload["readonly"]).to be false + end + + it "returns resource_url in the result" do + result = service_call.result + + expect(result[:resource_url]).to include("/api/v3/documents/#{document.id}") + end + + it "returns readonly in the result" do + result = service_call.result + + expect(result[:readonly]).to be false end it "returns expires_at as ISO8601 timestamp" do @@ -64,6 +98,18 @@ RSpec.describe Documents::OAuth::TokenWithMetadataService, it "creates a new access token" do expect { service_call }.to change(Doorkeeper::AccessToken, :count).by(1) end + + context "when user only has view_documents permission (readonly)" do + let(:user) { create(:user, member_with_roles: { project => view_only_role }) } + + it "includes readonly: true in the packed params" do + result = service_call.result + decrypted = decrypt_token(result[:encrypted_token]) + payload = JSON.parse(decrypted) + + expect(payload["readonly"]).to be true + end + end end context "when token generation fails" do From 56ac78da8d69d21adfee2b1b1e8280b7bd21658f Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Wed, 21 Jan 2026 19:42:07 +0300 Subject: [PATCH 009/293] Filter refresh token response to only include relevant fields Apply least privilege principle: RefreshTokensController now returns only encrypted_token, expires_at, and expires_in_seconds. Excludes resource_url and readonly which are only needed for initial page load. --- .../controllers/documents/oauth/refresh_tokens_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/documents/app/controllers/documents/oauth/refresh_tokens_controller.rb b/modules/documents/app/controllers/documents/oauth/refresh_tokens_controller.rb index 408a994be97..da78ca2b76f 100644 --- a/modules/documents/app/controllers/documents/oauth/refresh_tokens_controller.rb +++ b/modules/documents/app/controllers/documents/oauth/refresh_tokens_controller.rb @@ -45,7 +45,7 @@ module Documents ).call if token_result.success? - render json: token_result.result, status: :ok + render json: token_result.result.slice(:encrypted_token, :expires_at, :expires_in_seconds), status: :ok else render json: { error: token_result.message }, status: :unprocessable_entity end From 81549eef519f2ec8ec9627c75ed81bd80d7858a5 Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Wed, 21 Jan 2026 19:49:02 +0300 Subject: [PATCH 010/293] Remove unused expires_at field from token refresh flow --- .../dynamic/documents/init-yjs-provider.controller.ts | 2 -- .../stimulus/services/documents/token-refresh.service.ts | 1 - .../show_edit_view/block_note_editor_component.html.erb | 1 - .../show_edit_view/block_note_editor_component.rb | 2 +- .../documents/oauth/refresh_tokens_controller.rb | 7 +++++-- modules/documents/app/controllers/documents_controller.rb | 1 - .../documents/oauth/token_with_metadata_service.rb | 1 - modules/documents/app/views/documents/show.html.erb | 1 - .../documents/oauth/refresh_tokens_controller_spec.rb | 5 ----- .../documents/oauth/token_with_metadata_service_spec.rb | 7 ------- 10 files changed, 6 insertions(+), 22 deletions(-) diff --git a/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts b/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts index ee7b99699b3..75ec46ea251 100644 --- a/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts @@ -40,7 +40,6 @@ export default class extends Controller { hocuspocusUrl: String, tokenPayload: String, documentName: String, - tokenExpiresAt: String, tokenExpiresInSeconds: Number, refreshUrl: String, }; @@ -48,7 +47,6 @@ export default class extends Controller { declare readonly hocuspocusUrlValue:string; declare readonly tokenPayloadValue:string; declare readonly documentNameValue:string; - declare readonly tokenExpiresAtValue:string; declare readonly tokenExpiresInSecondsValue:number; declare readonly refreshUrlValue:string; diff --git a/frontend/src/stimulus/services/documents/token-refresh.service.ts b/frontend/src/stimulus/services/documents/token-refresh.service.ts index fe71e31b5be..eb7e76157fa 100644 --- a/frontend/src/stimulus/services/documents/token-refresh.service.ts +++ b/frontend/src/stimulus/services/documents/token-refresh.service.ts @@ -33,7 +33,6 @@ import { getMetaContent } from 'core-app/core/setup/globals/global-helpers'; export interface TokenResponse { encrypted_token:string; - expires_at:string; expires_in_seconds:number; } diff --git a/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.html.erb b/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.html.erb index 480f65be8ab..8742bb1f871 100644 --- a/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.html.erb +++ b/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.html.erb @@ -35,7 +35,6 @@ "documents--init-yjs-provider-hocuspocus-url-value": Setting.collaborative_editing_hocuspocus_url, "documents--init-yjs-provider-token-payload-value": token_payload, "documents--init-yjs-provider-document-name-value": resource_url, - "documents--init-yjs-provider-token-expires-at-value": token_expires_at, "documents--init-yjs-provider-token-expires-in-seconds-value": token_expires_in_seconds, "documents--init-yjs-provider-refresh-url-value": refresh_token_url ) diff --git a/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.rb b/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.rb index df0de565812..206262fddc3 100644 --- a/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.rb +++ b/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.rb @@ -36,7 +36,7 @@ module Documents alias_method :document, :model - options :project, :token_payload, :resource_url, :token_expires_at, :token_expires_in_seconds, :state, :readonly + options :project, :token_payload, :resource_url, :token_expires_in_seconds, :state, :readonly private diff --git a/modules/documents/app/controllers/documents/oauth/refresh_tokens_controller.rb b/modules/documents/app/controllers/documents/oauth/refresh_tokens_controller.rb index da78ca2b76f..4fbbc90075c 100644 --- a/modules/documents/app/controllers/documents/oauth/refresh_tokens_controller.rb +++ b/modules/documents/app/controllers/documents/oauth/refresh_tokens_controller.rb @@ -45,7 +45,7 @@ module Documents ).call if token_result.success? - render json: token_result.result.slice(:encrypted_token, :expires_at, :expires_in_seconds), status: :ok + render json: token_result.result.slice(:encrypted_token, :expires_in_seconds), status: :ok else render json: { error: token_result.message }, status: :unprocessable_entity end @@ -53,7 +53,10 @@ module Documents private - def find_model_object(object_id = :document_id) = super + def find_model_object(object_id = :document_id) + super + @document = @object + end end end end diff --git a/modules/documents/app/controllers/documents_controller.rb b/modules/documents/app/controllers/documents_controller.rb index 5a14a90522f..7a3e9399243 100644 --- a/modules/documents/app/controllers/documents_controller.rb +++ b/modules/documents/app/controllers/documents_controller.rb @@ -236,7 +236,6 @@ class DocumentsController < ApplicationController @token_payload = token_result.result[:encrypted_token] @resource_url = token_result.result[:resource_url] @readonly = token_result.result[:readonly] - @token_expires_at = token_result.result[:expires_at] @token_expires_in_seconds = token_result.result[:expires_in_seconds] end diff --git a/modules/documents/app/services/documents/oauth/token_with_metadata_service.rb b/modules/documents/app/services/documents/oauth/token_with_metadata_service.rb index 52606e7375e..7038ef16905 100644 --- a/modules/documents/app/services/documents/oauth/token_with_metadata_service.rb +++ b/modules/documents/app/services/documents/oauth/token_with_metadata_service.rb @@ -61,7 +61,6 @@ module Documents encrypted_token: encrypt_result.result, resource_url:, readonly:, - expires_at: access_token.expires_in.seconds.from_now.iso8601, expires_in_seconds: access_token.expires_in } ) diff --git a/modules/documents/app/views/documents/show.html.erb b/modules/documents/app/views/documents/show.html.erb index f0daa4be93c..9217cacac48 100644 --- a/modules/documents/app/views/documents/show.html.erb +++ b/modules/documents/app/views/documents/show.html.erb @@ -40,7 +40,6 @@ project: @project, token_payload: @token_payload, resource_url: @resource_url, - token_expires_at: @token_expires_at, token_expires_in_seconds: @token_expires_in_seconds, state: @state, readonly: @readonly diff --git a/modules/documents/spec/controllers/documents/oauth/refresh_tokens_controller_spec.rb b/modules/documents/spec/controllers/documents/oauth/refresh_tokens_controller_spec.rb index 876d7fa01c9..d454229222f 100644 --- a/modules/documents/spec/controllers/documents/oauth/refresh_tokens_controller_spec.rb +++ b/modules/documents/spec/controllers/documents/oauth/refresh_tokens_controller_spec.rb @@ -81,12 +81,7 @@ RSpec.describe Documents::OAuth::RefreshTokensController do aggregate_failures "returns token metadata" do expect(json).to have_key("encrypted_token") - expect(json).to have_key("expires_at") expect(json).to have_key("expires_in_seconds") - end - - aggregate_failures "valid expiration values" do - expect { Time.iso8601(json["expires_at"]) }.not_to raise_error expect(json["expires_in_seconds"]).to eq(5.minutes.to_i) end end diff --git a/modules/documents/spec/services/documents/oauth/token_with_metadata_service_spec.rb b/modules/documents/spec/services/documents/oauth/token_with_metadata_service_spec.rb index d8aaca28087..2e1579afb23 100644 --- a/modules/documents/spec/services/documents/oauth/token_with_metadata_service_spec.rb +++ b/modules/documents/spec/services/documents/oauth/token_with_metadata_service_spec.rb @@ -82,13 +82,6 @@ RSpec.describe Documents::OAuth::TokenWithMetadataService, expect(result[:readonly]).to be false end - it "returns expires_at as ISO8601 timestamp" do - result = service_call.result - - expect(result[:expires_at]).to be_a(String) - expect { Time.iso8601(result[:expires_at]) }.not_to raise_error - end - it "returns expires_in_seconds matching the token expiry" do result = service_call.result From fdc7696e8ada6f5d11e76290a2488d8fb37ae2aa Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Wed, 21 Jan 2026 20:26:23 +0300 Subject: [PATCH 011/293] Fix duplicate token refresh requests When TokenRefreshService refreshes the token and calls sendToken(), the HocuspocusProvider's token callback (getToken) was being invoked. Since canUseCachedToken was false, it would fetch another token, causing duplicate requests. Fix: Set canUseCachedToken = true in the refresh callback so that sendToken() uses the just-refreshed cached token instead of fetching again. Also add safeguard to destroy any existing TokenRefreshService in connect() to prevent duplicate timers if the controller reconnects. --- .../dynamic/documents/init-yjs-provider.controller.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts b/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts index 75ec46ea251..794d7086cd6 100644 --- a/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts @@ -85,10 +85,15 @@ export default class extends Controller { LiveCollaborationManager.initializeYjsProvider(provider, ydoc); if (this.refreshUrlValue && this.tokenExpiresInSecondsValue) { + // Destroy any existing service to prevent duplicate timers if connect() is called multiple times + this.tokenRefreshService?.destroy(); this.tokenRefreshService = new TokenRefreshService( provider, this.refreshUrlValue, - (newToken) => { this.currentToken = newToken; }, + (newToken) => { + this.currentToken = newToken; + this.canUseCachedToken = true; + }, ); this.tokenRefreshService.scheduleRefresh(this.tokenExpiresInSecondsValue); } From 7d9bea6866da39143d208cdca3ae9aed665d60d3 Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Thu, 22 Jan 2026 12:41:32 +0300 Subject: [PATCH 012/293] Refine token services --- .../oauth/token_with_metadata_service.rb | 28 +++++++++++++------ .../app/views/documents/show.html.erb | 2 +- .../oauth/token_with_metadata_service_spec.rb | 14 ++++++++-- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/modules/documents/app/services/documents/oauth/token_with_metadata_service.rb b/modules/documents/app/services/documents/oauth/token_with_metadata_service.rb index 7038ef16905..cffea039bcc 100644 --- a/modules/documents/app/services/documents/oauth/token_with_metadata_service.rb +++ b/modules/documents/app/services/documents/oauth/token_with_metadata_service.rb @@ -33,6 +33,8 @@ module Documents class TokenWithMetadataService < BaseServices::BaseCallable include API::V3::Utilities::PathHelper + attr_reader :user, :document, :project + def initialize(user:, document:, project:) super() @@ -41,9 +43,13 @@ module Documents @project = project end - def perform - token_result = GenerateTokenService.new(user: @user).call - return token_result unless token_result.success? + def perform # rubocop:disable Metrics/AbcSize + token_result = GenerateTokenService.new(user:).call + + if token_result.failure? + Rails.logger.error("Failed to generate OAuth token for document #{document.id}: #{token_result.errors}") + return token_result + end access_token = token_result.result @@ -53,12 +59,16 @@ module Documents readonly: } - encrypt_result = EncryptTokenService.new(token: payload.to_json).call - return encrypt_result unless encrypt_result.success? + encrypted_result = EncryptTokenService.new(token: payload.to_json).call + + if encrypted_result.failure? + Rails.logger.error("Failed to encrypt OAuth token payload for document #{document.id}: #{encrypted_result.errors}") + return encrypted_result + end ServiceResult.success( result: { - encrypted_token: encrypt_result.result, + encrypted_token: encrypted_result.result, resource_url:, readonly:, expires_in_seconds: access_token.expires_in @@ -71,13 +81,13 @@ module Documents def resource_url @resource_url ||= URI.join( OpenProject::StaticRouting::StaticUrlHelpers.new.root_url, - api_v3_paths.document(@document.id) + api_v3_paths.document(document.id) ).to_s end def readonly - @readonly ||= @user.allowed_in_project?(:view_documents, @project) && - !@user.allowed_in_project?(:manage_documents, @project) + @readonly ||= user.allowed_in_project?(:view_documents, project) && + !user.allowed_in_project?(:manage_documents, project) end end end diff --git a/modules/documents/app/views/documents/show.html.erb b/modules/documents/app/views/documents/show.html.erb index 9217cacac48..6586e2f8777 100644 --- a/modules/documents/app/views/documents/show.html.erb +++ b/modules/documents/app/views/documents/show.html.erb @@ -39,9 +39,9 @@ @document, project: @project, token_payload: @token_payload, + state: @state, resource_url: @resource_url, token_expires_in_seconds: @token_expires_in_seconds, - state: @state, readonly: @readonly ) ) diff --git a/modules/documents/spec/services/documents/oauth/token_with_metadata_service_spec.rb b/modules/documents/spec/services/documents/oauth/token_with_metadata_service_spec.rb index 2e1579afb23..c695b6cd7ad 100644 --- a/modules/documents/spec/services/documents/oauth/token_with_metadata_service_spec.rb +++ b/modules/documents/spec/services/documents/oauth/token_with_metadata_service_spec.rb @@ -110,10 +110,15 @@ RSpec.describe Documents::OAuth::TokenWithMetadataService, allow_any_instance_of(Documents::OAuth::GenerateTokenService) # rubocop:disable RSpec/AnyInstance .to receive(:call) .and_return(ServiceResult.failure(errors: "Token generation failed")) + + allow(Rails.logger).to receive(:error) end - it "returns a failure" do + it "returns a failure, logs error message" do expect(service_call).to be_failure + + expect(Rails.logger).to have_received(:error) + .with("Failed to generate OAuth token for document #{document.id}: Token generation failed") end end @@ -122,10 +127,15 @@ RSpec.describe Documents::OAuth::TokenWithMetadataService, allow(Setting) .to receive(:collaborative_editing_hocuspocus_secret) .and_return(nil) + + allow(Rails.logger).to receive(:error) end - it "returns a failure" do + it "returns a failure, logs error message" do expect(service_call).to be_failure + expect(Rails.logger).to have_received(:error) + .with("Failed to encrypt OAuth token payload for document #{document.id}: " \ + "Collaborative editing secret is not set. Cannot encrypt token.") end end end From 170bbbbf5cd10c1c01139dc90a5c031c262f6073 Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Mon, 26 Jan 2026 12:08:22 +0300 Subject: [PATCH 013/293] Revert "Remove unused expires_at field from token refresh flow" This reverts commit 81549eef519f2ec8ec9627c75ed81bd80d7858a5. --- .../documents/init-yjs-provider.controller.ts | 2 ++ .../services/documents/token-refresh.service.ts | 1 + .../block_note_editor_component.html.erb | 1 + .../show_edit_view/block_note_editor_component.rb | 2 +- .../documents/oauth/refresh_tokens_controller.rb | 2 +- .../app/controllers/documents_controller.rb | 1 + .../documents/oauth/token_with_metadata_service.rb | 3 +++ modules/documents/app/views/documents/show.html.erb | 1 + .../oauth/refresh_tokens_controller_spec.rb | 5 +++++ .../oauth/token_with_metadata_service_spec.rb | 12 +++++++++++- 10 files changed, 27 insertions(+), 3 deletions(-) diff --git a/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts b/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts index 794d7086cd6..e6b9c776462 100644 --- a/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts @@ -40,6 +40,7 @@ export default class extends Controller { hocuspocusUrl: String, tokenPayload: String, documentName: String, + tokenExpiresAt: String, tokenExpiresInSeconds: Number, refreshUrl: String, }; @@ -47,6 +48,7 @@ export default class extends Controller { declare readonly hocuspocusUrlValue:string; declare readonly tokenPayloadValue:string; declare readonly documentNameValue:string; + declare readonly tokenExpiresAtValue:string; declare readonly tokenExpiresInSecondsValue:number; declare readonly refreshUrlValue:string; diff --git a/frontend/src/stimulus/services/documents/token-refresh.service.ts b/frontend/src/stimulus/services/documents/token-refresh.service.ts index eb7e76157fa..fe71e31b5be 100644 --- a/frontend/src/stimulus/services/documents/token-refresh.service.ts +++ b/frontend/src/stimulus/services/documents/token-refresh.service.ts @@ -33,6 +33,7 @@ import { getMetaContent } from 'core-app/core/setup/globals/global-helpers'; export interface TokenResponse { encrypted_token:string; + expires_at:string; expires_in_seconds:number; } diff --git a/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.html.erb b/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.html.erb index 8742bb1f871..480f65be8ab 100644 --- a/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.html.erb +++ b/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.html.erb @@ -35,6 +35,7 @@ "documents--init-yjs-provider-hocuspocus-url-value": Setting.collaborative_editing_hocuspocus_url, "documents--init-yjs-provider-token-payload-value": token_payload, "documents--init-yjs-provider-document-name-value": resource_url, + "documents--init-yjs-provider-token-expires-at-value": token_expires_at, "documents--init-yjs-provider-token-expires-in-seconds-value": token_expires_in_seconds, "documents--init-yjs-provider-refresh-url-value": refresh_token_url ) diff --git a/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.rb b/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.rb index 206262fddc3..df0de565812 100644 --- a/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.rb +++ b/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.rb @@ -36,7 +36,7 @@ module Documents alias_method :document, :model - options :project, :token_payload, :resource_url, :token_expires_in_seconds, :state, :readonly + options :project, :token_payload, :resource_url, :token_expires_at, :token_expires_in_seconds, :state, :readonly private diff --git a/modules/documents/app/controllers/documents/oauth/refresh_tokens_controller.rb b/modules/documents/app/controllers/documents/oauth/refresh_tokens_controller.rb index 4fbbc90075c..bb229571f69 100644 --- a/modules/documents/app/controllers/documents/oauth/refresh_tokens_controller.rb +++ b/modules/documents/app/controllers/documents/oauth/refresh_tokens_controller.rb @@ -45,7 +45,7 @@ module Documents ).call if token_result.success? - render json: token_result.result.slice(:encrypted_token, :expires_in_seconds), status: :ok + render json: token_result.result.slice(:encrypted_token, :expires_at, :expires_in_seconds), status: :ok else render json: { error: token_result.message }, status: :unprocessable_entity end diff --git a/modules/documents/app/controllers/documents_controller.rb b/modules/documents/app/controllers/documents_controller.rb index 7a3e9399243..5a14a90522f 100644 --- a/modules/documents/app/controllers/documents_controller.rb +++ b/modules/documents/app/controllers/documents_controller.rb @@ -236,6 +236,7 @@ class DocumentsController < ApplicationController @token_payload = token_result.result[:encrypted_token] @resource_url = token_result.result[:resource_url] @readonly = token_result.result[:readonly] + @token_expires_at = token_result.result[:expires_at] @token_expires_in_seconds = token_result.result[:expires_in_seconds] end diff --git a/modules/documents/app/services/documents/oauth/token_with_metadata_service.rb b/modules/documents/app/services/documents/oauth/token_with_metadata_service.rb index cffea039bcc..91342f767b3 100644 --- a/modules/documents/app/services/documents/oauth/token_with_metadata_service.rb +++ b/modules/documents/app/services/documents/oauth/token_with_metadata_service.rb @@ -52,10 +52,12 @@ module Documents end access_token = token_result.result + expires_at = access_token.expires_in.seconds.from_now.iso8601 payload = { resource_url:, oauth_token: access_token.plaintext_token, + expires_at:, readonly: } @@ -71,6 +73,7 @@ module Documents encrypted_token: encrypted_result.result, resource_url:, readonly:, + expires_at:, expires_in_seconds: access_token.expires_in } ) diff --git a/modules/documents/app/views/documents/show.html.erb b/modules/documents/app/views/documents/show.html.erb index 6586e2f8777..98e3f64af98 100644 --- a/modules/documents/app/views/documents/show.html.erb +++ b/modules/documents/app/views/documents/show.html.erb @@ -41,6 +41,7 @@ token_payload: @token_payload, state: @state, resource_url: @resource_url, + token_expires_at: @token_expires_at, token_expires_in_seconds: @token_expires_in_seconds, readonly: @readonly ) diff --git a/modules/documents/spec/controllers/documents/oauth/refresh_tokens_controller_spec.rb b/modules/documents/spec/controllers/documents/oauth/refresh_tokens_controller_spec.rb index d454229222f..876d7fa01c9 100644 --- a/modules/documents/spec/controllers/documents/oauth/refresh_tokens_controller_spec.rb +++ b/modules/documents/spec/controllers/documents/oauth/refresh_tokens_controller_spec.rb @@ -81,7 +81,12 @@ RSpec.describe Documents::OAuth::RefreshTokensController do aggregate_failures "returns token metadata" do expect(json).to have_key("encrypted_token") + expect(json).to have_key("expires_at") expect(json).to have_key("expires_in_seconds") + end + + aggregate_failures "valid expiration values" do + expect { Time.iso8601(json["expires_at"]) }.not_to raise_error expect(json["expires_in_seconds"]).to eq(5.minutes.to_i) end end diff --git a/modules/documents/spec/services/documents/oauth/token_with_metadata_service_spec.rb b/modules/documents/spec/services/documents/oauth/token_with_metadata_service_spec.rb index c695b6cd7ad..39c19fa0469 100644 --- a/modules/documents/spec/services/documents/oauth/token_with_metadata_service_spec.rb +++ b/modules/documents/spec/services/documents/oauth/token_with_metadata_service_spec.rb @@ -55,11 +55,13 @@ RSpec.describe Documents::OAuth::TokenWithMetadataService, expect(service_call).to be_success end - it "returns an encrypted token containing packed params" do + it "returns an encrypted token containing packed params", + freeze_time: DateTime.parse("2025-01-04T9:00:00Z") do result = service_call.result expect(result[:encrypted_token]).to be_a(String) expect(result[:encrypted_token]).not_to be_empty + expect(result[:expires_at]).to eq("2025-01-04T09:05:00Z") # Verify the encrypted token contains packed params by decrypting decrypted = decrypt_token(result[:encrypted_token]) @@ -68,6 +70,7 @@ RSpec.describe Documents::OAuth::TokenWithMetadataService, expect(payload["resource_url"]).to include("/api/v3/documents/#{document.id}") expect(payload["oauth_token"]).to be_present expect(payload["readonly"]).to be false + expect(payload["expires_at"]).to eq("2025-01-04T09:05:00Z") end it "returns resource_url in the result" do @@ -82,6 +85,13 @@ RSpec.describe Documents::OAuth::TokenWithMetadataService, expect(result[:readonly]).to be false end + it "returns expires_at as ISO8601 timestamp" do + result = service_call.result + + expect(result[:expires_at]).to be_a(String) + expect { Time.iso8601(result[:expires_at]) }.not_to raise_error + end + it "returns expires_in_seconds matching the token expiry" do result = service_call.result From 267c57db23f223d18bfeb2a0b22542d38866f30d Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Mon, 26 Jan 2026 19:20:41 +0300 Subject: [PATCH 014/293] Refactor TokenRefreshService to DRY up scheduling logic Extract common setTimeout logic into scheduleRefreshAfter() method, reducing duplication between scheduleRefresh() and scheduleRetry(). --- .../documents/token-refresh.service.ts | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/frontend/src/stimulus/services/documents/token-refresh.service.ts b/frontend/src/stimulus/services/documents/token-refresh.service.ts index fe71e31b5be..7b49e919593 100644 --- a/frontend/src/stimulus/services/documents/token-refresh.service.ts +++ b/frontend/src/stimulus/services/documents/token-refresh.service.ts @@ -106,18 +106,12 @@ export class TokenRefreshService { } scheduleRefresh(expiresInSeconds:number):void { - this.clearTimer(); this.retryCount = 0; - - if (this.destroyed) { - return; - } - - const refreshDelayMs = Math.max(MIN_REFRESH_DELAY_MS, Math.floor(expiresInSeconds * REFRESH_THRESHOLD * 1000)); - - this.refreshTimer = setTimeout(() => { - void this.performRefresh(); - }, refreshDelayMs); + const delayMs = Math.max( + MIN_REFRESH_DELAY_MS, + Math.floor(expiresInSeconds * REFRESH_THRESHOLD * 1000), + ); + this.scheduleRefreshAfter(delayMs); } static async fetchToken(refreshUrl:string):Promise { @@ -183,6 +177,10 @@ export class TokenRefreshService { } private scheduleRetry():void { + this.scheduleRefreshAfter(RETRY_DELAY_MS); + } + + private scheduleRefreshAfter(delayMs:number):void { this.clearTimer(); if (this.destroyed) { @@ -191,7 +189,7 @@ export class TokenRefreshService { this.refreshTimer = setTimeout(() => { void this.performRefresh(); - }, RETRY_DELAY_MS); + }, delayMs); } private clearTimer():void { From 6cdab7b170ca7611fb3d2067e69b4b70f322838c Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Mon, 26 Jan 2026 20:18:48 +0300 Subject: [PATCH 015/293] Remove documents refresh token from oauth route Documents refresh tokens are not OAuth specifi; they are session bound and hence do not fit correctly within the "oauth" route --- .../block_note_editor_component.rb | 2 +- .../{oauth => }/refresh_tokens_controller.rb | 42 +++++++++---------- modules/documents/config/routes.rb | 4 +- .../lib/open_project/documents/engine.rb | 2 +- .../refresh_tokens_controller_spec.rb | 4 +- 5 files changed, 25 insertions(+), 29 deletions(-) rename modules/documents/app/controllers/documents/{oauth => }/refresh_tokens_controller.rb (62%) rename modules/documents/spec/controllers/documents/{oauth => }/refresh_tokens_controller_spec.rb (97%) diff --git a/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.rb b/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.rb index df0de565812..b5f41c920dc 100644 --- a/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.rb +++ b/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.rb @@ -41,7 +41,7 @@ module Documents private def refresh_token_url - document_oauth_refresh_token_path(document) + document_refresh_token_path(document) end end end diff --git a/modules/documents/app/controllers/documents/oauth/refresh_tokens_controller.rb b/modules/documents/app/controllers/documents/refresh_tokens_controller.rb similarity index 62% rename from modules/documents/app/controllers/documents/oauth/refresh_tokens_controller.rb rename to modules/documents/app/controllers/documents/refresh_tokens_controller.rb index bb229571f69..3270e6959e1 100644 --- a/modules/documents/app/controllers/documents/oauth/refresh_tokens_controller.rb +++ b/modules/documents/app/controllers/documents/refresh_tokens_controller.rb @@ -29,34 +29,32 @@ #++ module Documents - module OAuth - class RefreshTokensController < ApplicationController - model_object Document + class RefreshTokensController < ApplicationController + model_object Document - before_action :find_model_object - before_action :find_project_from_association - before_action :authorize + before_action :find_model_object + before_action :find_project_from_association + before_action :authorize - def create - token_result = TokenWithMetadataService.new( - user: current_user, - document: @document, - project: @project - ).call + def create + token_result = Documents::OAuth::TokenWithMetadataService.new( + user: current_user, + document: @document, + project: @project + ).call - if token_result.success? - render json: token_result.result.slice(:encrypted_token, :expires_at, :expires_in_seconds), status: :ok - else - render json: { error: token_result.message }, status: :unprocessable_entity - end + if token_result.success? + render json: token_result.result.slice(:encrypted_token, :expires_at, :expires_in_seconds), status: :ok + else + render json: { error: token_result.message }, status: :unprocessable_entity end + end - private + private - def find_model_object(object_id = :document_id) - super - @document = @object - end + def find_model_object(object_id = :document_id) + super + @document = @object end end end diff --git a/modules/documents/config/routes.rb b/modules/documents/config/routes.rb index 047b028d16a..b5ad7ca3e17 100644 --- a/modules/documents/config/routes.rb +++ b/modules/documents/config/routes.rb @@ -51,9 +51,7 @@ Rails.application.routes.draw do get :render_connection_recovery, defaults: { format: :turbo_stream } end - namespace :oauth do - resource :refresh_token, only: [:create], controller: "/documents/oauth/refresh_tokens", defaults: { format: :json } - end + resource :refresh_token, only: [:create], controller: "documents/refresh_tokens", defaults: { format: :json } end scope module: :documents do diff --git a/modules/documents/lib/open_project/documents/engine.rb b/modules/documents/lib/open_project/documents/engine.rb index c43d5916b25..a7affa56423 100644 --- a/modules/documents/lib/open_project/documents/engine.rb +++ b/modules/documents/lib/open_project/documents/engine.rb @@ -60,7 +60,7 @@ module OpenProject::Documents render_connection_error render_connection_recovery ], "documents/menus": %i[show], - "documents/oauth/refresh_tokens": %i[create] + "documents/refresh_tokens": %i[create] }, permissible_on: :project permission :manage_documents, diff --git a/modules/documents/spec/controllers/documents/oauth/refresh_tokens_controller_spec.rb b/modules/documents/spec/controllers/documents/refresh_tokens_controller_spec.rb similarity index 97% rename from modules/documents/spec/controllers/documents/oauth/refresh_tokens_controller_spec.rb rename to modules/documents/spec/controllers/documents/refresh_tokens_controller_spec.rb index 876d7fa01c9..c8cb8dd206f 100644 --- a/modules/documents/spec/controllers/documents/oauth/refresh_tokens_controller_spec.rb +++ b/modules/documents/spec/controllers/documents/refresh_tokens_controller_spec.rb @@ -30,7 +30,7 @@ require "spec_helper" -RSpec.describe Documents::OAuth::RefreshTokensController do +RSpec.describe Documents::RefreshTokensController do let(:project) { create(:project) } let(:document) { create(:document, project:) } let(:user) { create(:user) } @@ -47,7 +47,7 @@ RSpec.describe Documents::OAuth::RefreshTokensController do it "redirects to login" do post :create, params: { document_id: document.id } - expect(response).to redirect_to(signin_path(back_url: document_oauth_refresh_token_url(document))) + expect(response).to redirect_to(signin_path(back_url: document_refresh_token_url(document))) end end From 2e05672bffadda8ad4b1a1ac055377b6a648999d Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Mon, 26 Jan 2026 20:35:11 +0300 Subject: [PATCH 016/293] Remove unused `expires_at` attribute from dom --- .../dynamic/documents/init-yjs-provider.controller.ts | 2 -- .../show_edit_view/block_note_editor_component.html.erb | 1 - .../show_edit_view/block_note_editor_component.rb | 2 +- .../controllers/documents/refresh_tokens_controller.rb | 2 +- modules/documents/app/controllers/documents_controller.rb | 1 - modules/documents/app/views/documents/show.html.erb | 1 - .../documents/refresh_tokens_controller_spec.rb | 2 -- .../documents/oauth/token_with_metadata_service_spec.rb | 8 -------- 8 files changed, 2 insertions(+), 17 deletions(-) diff --git a/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts b/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts index e6b9c776462..794d7086cd6 100644 --- a/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/documents/init-yjs-provider.controller.ts @@ -40,7 +40,6 @@ export default class extends Controller { hocuspocusUrl: String, tokenPayload: String, documentName: String, - tokenExpiresAt: String, tokenExpiresInSeconds: Number, refreshUrl: String, }; @@ -48,7 +47,6 @@ export default class extends Controller { declare readonly hocuspocusUrlValue:string; declare readonly tokenPayloadValue:string; declare readonly documentNameValue:string; - declare readonly tokenExpiresAtValue:string; declare readonly tokenExpiresInSecondsValue:number; declare readonly refreshUrlValue:string; diff --git a/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.html.erb b/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.html.erb index 480f65be8ab..8742bb1f871 100644 --- a/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.html.erb +++ b/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.html.erb @@ -35,7 +35,6 @@ "documents--init-yjs-provider-hocuspocus-url-value": Setting.collaborative_editing_hocuspocus_url, "documents--init-yjs-provider-token-payload-value": token_payload, "documents--init-yjs-provider-document-name-value": resource_url, - "documents--init-yjs-provider-token-expires-at-value": token_expires_at, "documents--init-yjs-provider-token-expires-in-seconds-value": token_expires_in_seconds, "documents--init-yjs-provider-refresh-url-value": refresh_token_url ) diff --git a/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.rb b/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.rb index b5f41c920dc..f7ee5831ad9 100644 --- a/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.rb +++ b/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.rb @@ -36,7 +36,7 @@ module Documents alias_method :document, :model - options :project, :token_payload, :resource_url, :token_expires_at, :token_expires_in_seconds, :state, :readonly + options :project, :token_payload, :resource_url, :token_expires_in_seconds, :state, :readonly private diff --git a/modules/documents/app/controllers/documents/refresh_tokens_controller.rb b/modules/documents/app/controllers/documents/refresh_tokens_controller.rb index 3270e6959e1..bbaa1fa73cc 100644 --- a/modules/documents/app/controllers/documents/refresh_tokens_controller.rb +++ b/modules/documents/app/controllers/documents/refresh_tokens_controller.rb @@ -44,7 +44,7 @@ module Documents ).call if token_result.success? - render json: token_result.result.slice(:encrypted_token, :expires_at, :expires_in_seconds), status: :ok + render json: token_result.result.slice(:encrypted_token, :expires_in_seconds), status: :ok else render json: { error: token_result.message }, status: :unprocessable_entity end diff --git a/modules/documents/app/controllers/documents_controller.rb b/modules/documents/app/controllers/documents_controller.rb index 5a14a90522f..7a3e9399243 100644 --- a/modules/documents/app/controllers/documents_controller.rb +++ b/modules/documents/app/controllers/documents_controller.rb @@ -236,7 +236,6 @@ class DocumentsController < ApplicationController @token_payload = token_result.result[:encrypted_token] @resource_url = token_result.result[:resource_url] @readonly = token_result.result[:readonly] - @token_expires_at = token_result.result[:expires_at] @token_expires_in_seconds = token_result.result[:expires_in_seconds] end diff --git a/modules/documents/app/views/documents/show.html.erb b/modules/documents/app/views/documents/show.html.erb index 98e3f64af98..6586e2f8777 100644 --- a/modules/documents/app/views/documents/show.html.erb +++ b/modules/documents/app/views/documents/show.html.erb @@ -41,7 +41,6 @@ token_payload: @token_payload, state: @state, resource_url: @resource_url, - token_expires_at: @token_expires_at, token_expires_in_seconds: @token_expires_in_seconds, readonly: @readonly ) diff --git a/modules/documents/spec/controllers/documents/refresh_tokens_controller_spec.rb b/modules/documents/spec/controllers/documents/refresh_tokens_controller_spec.rb index c8cb8dd206f..c0b80a58028 100644 --- a/modules/documents/spec/controllers/documents/refresh_tokens_controller_spec.rb +++ b/modules/documents/spec/controllers/documents/refresh_tokens_controller_spec.rb @@ -81,12 +81,10 @@ RSpec.describe Documents::RefreshTokensController do aggregate_failures "returns token metadata" do expect(json).to have_key("encrypted_token") - expect(json).to have_key("expires_at") expect(json).to have_key("expires_in_seconds") end aggregate_failures "valid expiration values" do - expect { Time.iso8601(json["expires_at"]) }.not_to raise_error expect(json["expires_in_seconds"]).to eq(5.minutes.to_i) end end diff --git a/modules/documents/spec/services/documents/oauth/token_with_metadata_service_spec.rb b/modules/documents/spec/services/documents/oauth/token_with_metadata_service_spec.rb index 39c19fa0469..bc74b069c11 100644 --- a/modules/documents/spec/services/documents/oauth/token_with_metadata_service_spec.rb +++ b/modules/documents/spec/services/documents/oauth/token_with_metadata_service_spec.rb @@ -61,7 +61,6 @@ RSpec.describe Documents::OAuth::TokenWithMetadataService, expect(result[:encrypted_token]).to be_a(String) expect(result[:encrypted_token]).not_to be_empty - expect(result[:expires_at]).to eq("2025-01-04T09:05:00Z") # Verify the encrypted token contains packed params by decrypting decrypted = decrypt_token(result[:encrypted_token]) @@ -85,13 +84,6 @@ RSpec.describe Documents::OAuth::TokenWithMetadataService, expect(result[:readonly]).to be false end - it "returns expires_at as ISO8601 timestamp" do - result = service_call.result - - expect(result[:expires_at]).to be_a(String) - expect { Time.iso8601(result[:expires_at]) }.not_to raise_error - end - it "returns expires_in_seconds matching the token expiry" do result = service_call.result From 7fba5cadbf43ef9bd5022b7052d7de0485fc0991 Mon Sep 17 00:00:00 2001 From: ulferts Date: Tue, 27 Jan 2026 17:58:10 +0100 Subject: [PATCH 017/293] display cf type in page title --- .../admin/custom_fields/edit_form_header_component.html.erb | 2 +- .../admin/custom_fields/edit_form_header_component.rb | 5 +++++ .../edit_form_header_component.html.erb | 2 +- .../project_custom_fields/edit_form_header_component.rb | 5 +++++ .../project_custom_fields/new_form_header_component.html.erb | 2 +- .../project_custom_fields/new_form_header_component.rb | 5 +++++ app/views/custom_fields/new.html.erb | 5 ++++- 7 files changed, 22 insertions(+), 4 deletions(-) diff --git a/app/components/admin/custom_fields/edit_form_header_component.html.erb b/app/components/admin/custom_fields/edit_form_header_component.html.erb index 66808da18b3..eafe33fff11 100644 --- a/app/components/admin/custom_fields/edit_form_header_component.html.erb +++ b/app/components/admin/custom_fields/edit_form_header_component.html.erb @@ -29,7 +29,7 @@ See COPYRIGHT and LICENSE files for more details. <%= render(Primer::OpenProject::PageHeader.new(test_selector: "custom-fields--page-header")) do |header| - header.with_title { @custom_field.attribute_in_database("name") } + header.with_title { page_title } header.with_breadcrumbs(breadcrumbs_items) diff --git a/app/components/admin/custom_fields/edit_form_header_component.rb b/app/components/admin/custom_fields/edit_form_header_component.rb index 15c3d514a86..6e0297750f3 100644 --- a/app/components/admin/custom_fields/edit_form_header_component.rb +++ b/app/components/admin/custom_fields/edit_form_header_component.rb @@ -76,6 +76,11 @@ module Admin private + def page_title + concat @custom_field.attribute_in_database("name") + concat render(Primer::Beta::Text.new(color: :muted)) { " (#{helpers.label_for_custom_field_format(@custom_field.field_format)})" } + end + def breadcrumbs_items [{ href: admin_index_path, text: t(:label_administration) }, { href: custom_fields_path, text: t(:label_custom_field_plural) }, diff --git a/app/components/settings/project_custom_fields/edit_form_header_component.html.erb b/app/components/settings/project_custom_fields/edit_form_header_component.html.erb index a9eeafa8c8c..818d7d61e17 100644 --- a/app/components/settings/project_custom_fields/edit_form_header_component.html.erb +++ b/app/components/settings/project_custom_fields/edit_form_header_component.html.erb @@ -28,7 +28,7 @@ See COPYRIGHT and LICENSE files for more details. ++#%> <%= render(Primer::OpenProject::PageHeader.new) do |header| - header.with_title { @custom_field.attribute_in_database("name") } + header.with_title { page_title } header.with_description { t("settings.project_attributes.edit.description") } unless hide_description? header.with_breadcrumbs(breadcrumbs_items) diff --git a/app/components/settings/project_custom_fields/edit_form_header_component.rb b/app/components/settings/project_custom_fields/edit_form_header_component.rb index 331fa5f2a1f..72e2df1c986 100644 --- a/app/components/settings/project_custom_fields/edit_form_header_component.rb +++ b/app/components/settings/project_custom_fields/edit_form_header_component.rb @@ -78,6 +78,11 @@ module Settings tabs end + def page_title + concat @custom_field.attribute_in_database("name") + concat render(Primer::Beta::Text.new(color: :muted)) { " (#{helpers.label_for_custom_field_format(@custom_field.field_format)})" } + end + def breadcrumbs_items [{ href: admin_index_path, text: t("label_administration") }, { href: admin_settings_project_custom_fields_path, text: t("label_project_plural") }, diff --git a/app/components/settings/project_custom_fields/new_form_header_component.html.erb b/app/components/settings/project_custom_fields/new_form_header_component.html.erb index 01e7fc54296..962383901ef 100644 --- a/app/components/settings/project_custom_fields/new_form_header_component.html.erb +++ b/app/components/settings/project_custom_fields/new_form_header_component.html.erb @@ -29,7 +29,7 @@ See COPYRIGHT and LICENSE files for more details. <%= render(Primer::OpenProject::PageHeader.new) do |header| - header.with_title { t("settings.project_attributes.new.heading") } + header.with_title { page_title } header.with_description { t("settings.project_attributes.new.description") } unless hide_description? header.with_breadcrumbs(breadcrumb_items, selected_item_font_weight: :normal) end diff --git a/app/components/settings/project_custom_fields/new_form_header_component.rb b/app/components/settings/project_custom_fields/new_form_header_component.rb index ef41034fb11..5595bcc5549 100644 --- a/app/components/settings/project_custom_fields/new_form_header_component.rb +++ b/app/components/settings/project_custom_fields/new_form_header_component.rb @@ -31,6 +31,11 @@ module Settings module ProjectCustomFields class NewFormHeaderComponent < ApplicationComponent + def page_title + concat t("settings.project_attributes.new.heading") + concat render(Primer::Beta::Text.new(color: :muted)) { " (#{helpers.label_for_custom_field_format(model.field_format)})" } + end + def breadcrumb_items [ { href: admin_index_path, text: t("label_administration") }, diff --git a/app/views/custom_fields/new.html.erb b/app/views/custom_fields/new.html.erb index feb38394b5c..47f84a9c93d 100644 --- a/app/views/custom_fields/new.html.erb +++ b/app/views/custom_fields/new.html.erb @@ -31,7 +31,10 @@ See COPYRIGHT and LICENSE files for more details. <%= render(Primer::OpenProject::PageHeader.new(test_selector: "custom-fields--page-header")) do |header| - header.with_title { t(:label_custom_field_new) } + header.with_title do + concat t(:label_custom_field_new) + concat render(Primer::Beta::Text.new(color: :muted)) { " (#{label_for_custom_field_format(@custom_field.field_format)})" } + end header.with_breadcrumbs( [{ href: admin_index_path, text: t(:label_administration) }, { href: custom_fields_path, text: t(:label_custom_field_plural) }, From 17d8ee3c54b2530014c861c46213ef37056ca465 Mon Sep 17 00:00:00 2001 From: ulferts Date: Wed, 28 Jan 2026 16:15:36 +0100 Subject: [PATCH 018/293] show type on edit form of custom fields --- .../admin/custom_fields/edit_form_header_component.rb | 11 +++++++---- .../edit_form_header_component.rb | 11 +++++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/app/components/admin/custom_fields/edit_form_header_component.rb b/app/components/admin/custom_fields/edit_form_header_component.rb index 6e0297750f3..00987bf65c2 100644 --- a/app/components/admin/custom_fields/edit_form_header_component.rb +++ b/app/components/admin/custom_fields/edit_form_header_component.rb @@ -82,10 +82,13 @@ module Admin end def breadcrumbs_items - [{ href: admin_index_path, text: t(:label_administration) }, - { href: custom_fields_path, text: t(:label_custom_field_plural) }, - { href: custom_fields_path(tab: @custom_field.type), text: I18n.t(@custom_field.type_name) }, - @custom_field.attribute_in_database("name")] + [ + { href: admin_index_path, text: t(:label_administration) }, + { href: custom_fields_path, text: t(:label_custom_field_plural) }, + { href: custom_fields_path(tab: @custom_field.type), text: I18n.t(@custom_field.type_name) }, + helpers.nested_breadcrumb_element(helpers.label_for_custom_field_format(model.field_format), + @custom_field.attribute_in_database("name")) + ] end end end diff --git a/app/components/settings/project_custom_fields/edit_form_header_component.rb b/app/components/settings/project_custom_fields/edit_form_header_component.rb index 72e2df1c986..4f52c6e58d1 100644 --- a/app/components/settings/project_custom_fields/edit_form_header_component.rb +++ b/app/components/settings/project_custom_fields/edit_form_header_component.rb @@ -84,10 +84,13 @@ module Settings end def breadcrumbs_items - [{ href: admin_index_path, text: t("label_administration") }, - { href: admin_settings_project_custom_fields_path, text: t("label_project_plural") }, - { href: admin_settings_project_custom_fields_path, text: t("settings.project_attributes.heading") }, - @custom_field.attribute_in_database("name")] + [ + { href: admin_index_path, text: t("label_administration") }, + { href: admin_settings_project_custom_fields_path, text: t("label_project_plural") }, + { href: admin_settings_project_custom_fields_path, text: t("settings.project_attributes.heading") }, + helpers.nested_breadcrumb_element(helpers.label_for_custom_field_format(@custom_field.field_format), + @custom_field.attribute_in_database("name")) + ] end def hide_description? From 48a7d863e4e1e14b3609f35194c87d5d81bd744a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Jan 2026 05:38:01 +0000 Subject: [PATCH 019/293] Bump the angular group in /frontend with 14 updates Bumps the angular group in /frontend with 14 updates: | Package | From | To | | --- | --- | --- | | [@angular/animations](https://github.com/angular/angular/tree/HEAD/packages/animations) | `21.1.1` | `21.1.2` | | [@angular/cdk](https://github.com/angular/components) | `21.1.1` | `21.1.2` | | [@angular/cli](https://github.com/angular/angular-cli) | `21.1.1` | `21.1.2` | | [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `21.1.1` | `21.1.2` | | [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) | `21.1.1` | `21.1.2` | | [@angular/compiler-cli](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli) | `21.1.1` | `21.1.2` | | [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `21.1.1` | `21.1.2` | | [@angular/elements](https://github.com/angular/angular/tree/HEAD/packages/elements) | `21.1.1` | `21.1.2` | | [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `21.1.1` | `21.1.2` | | [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `21.1.1` | `21.1.2` | | [@angular/platform-browser-dynamic](https://github.com/angular/angular/tree/HEAD/packages/platform-browser-dynamic) | `21.1.1` | `21.1.2` | | [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `21.1.1` | `21.1.2` | | [@angular-devkit/build-angular](https://github.com/angular/angular-cli) | `21.1.1` | `21.1.2` | | [@angular/language-service](https://github.com/angular/angular/tree/HEAD/packages/language-service) | `21.1.1` | `21.1.2` | Updates `@angular/animations` from 21.1.1 to 21.1.2 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.1.2/packages/animations) Updates `@angular/cdk` from 21.1.1 to 21.1.2 - [Release notes](https://github.com/angular/components/releases) - [Changelog](https://github.com/angular/components/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/components/compare/v21.1.1...v21.1.2) Updates `@angular/cli` from 21.1.1 to 21.1.2 - [Release notes](https://github.com/angular/angular-cli/releases) - [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular-cli/compare/v21.1.1...v21.1.2) Updates `@angular/common` from 21.1.1 to 21.1.2 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.1.2/packages/common) Updates `@angular/compiler` from 21.1.1 to 21.1.2 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.1.2/packages/compiler) Updates `@angular/compiler-cli` from 21.1.1 to 21.1.2 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.1.2/packages/compiler-cli) Updates `@angular/core` from 21.1.1 to 21.1.2 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.1.2/packages/core) Updates `@angular/elements` from 21.1.1 to 21.1.2 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.1.2/packages/elements) Updates `@angular/forms` from 21.1.1 to 21.1.2 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.1.2/packages/forms) Updates `@angular/platform-browser` from 21.1.1 to 21.1.2 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.1.2/packages/platform-browser) Updates `@angular/platform-browser-dynamic` from 21.1.1 to 21.1.2 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.1.2/packages/platform-browser-dynamic) Updates `@angular/router` from 21.1.1 to 21.1.2 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.1.2/packages/router) Updates `@angular-devkit/build-angular` from 21.1.1 to 21.1.2 - [Release notes](https://github.com/angular/angular-cli/releases) - [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular-cli/compare/v21.1.1...v21.1.2) Updates `@angular/language-service` from 21.1.1 to 21.1.2 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.1.2/packages/language-service) --- updated-dependencies: - dependency-name: "@angular/animations" dependency-version: 21.1.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/cdk" dependency-version: 21.1.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/cli" dependency-version: 21.1.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/common" dependency-version: 21.1.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/compiler" dependency-version: 21.1.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/compiler-cli" dependency-version: 21.1.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/core" dependency-version: 21.1.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/elements" dependency-version: 21.1.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/forms" dependency-version: 21.1.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/platform-browser" dependency-version: 21.1.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/platform-browser-dynamic" dependency-version: 21.1.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/router" dependency-version: 21.1.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular-devkit/build-angular" dependency-version: 21.1.2 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/language-service" dependency-version: 21.1.2 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: angular ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 516 ++++++++++++++++++------------------- frontend/package.json | 28 +- 2 files changed, 272 insertions(+), 272 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 1a900d802c9..3870447a3c9 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,18 +9,18 @@ "version": "0.1.0", "license": "GPLv3", "dependencies": { - "@angular/animations": "^21.1.1", - "@angular/cdk": "^21.1.1", - "@angular/cli": "^21.1.1", - "@angular/common": "^21.1.1", - "@angular/compiler": "^21.1.1", - "@angular/compiler-cli": "^21.1.1", - "@angular/core": "^21.1.1", - "@angular/elements": "^21.1.1", - "@angular/forms": "^21.1.1", - "@angular/platform-browser": "^21.1.1", - "@angular/platform-browser-dynamic": "^21.1.1", - "@angular/router": "^21.1.1", + "@angular/animations": "^21.1.2", + "@angular/cdk": "^21.1.2", + "@angular/cli": "^21.1.2", + "@angular/common": "^21.1.2", + "@angular/compiler": "^21.1.2", + "@angular/compiler-cli": "^21.1.2", + "@angular/core": "^21.1.2", + "@angular/elements": "^21.1.2", + "@angular/forms": "^21.1.2", + "@angular/platform-browser": "^21.1.2", + "@angular/platform-browser-dynamic": "^21.1.2", + "@angular/router": "^21.1.2", "@appsignal/javascript": "^1.6.1", "@appsignal/plugin-breadcrumbs-console": "^1.1.37", "@appsignal/plugin-breadcrumbs-network": "^1.1.24", @@ -129,13 +129,13 @@ }, "devDependencies": { "@angular-builders/custom-esbuild": "^21.0.3", - "@angular-devkit/build-angular": "^21.1.1", + "@angular-devkit/build-angular": "^21.1.2", "@angular-eslint/builder": "20.7.0", "@angular-eslint/eslint-plugin": "20.7.0", "@angular-eslint/eslint-plugin-template": "20.7.0", "@angular-eslint/schematics": "20.7.0", "@angular-eslint/template-parser": "20.7.0", - "@angular/language-service": "21.1.1", + "@angular/language-service": "21.1.2", "@eslint/js": "^9.39.2", "@html-eslint/eslint-plugin": "^0.54.0", "@html-eslint/parser": "^0.54.0", @@ -647,16 +647,16 @@ } }, "node_modules/@angular-devkit/build-angular": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-21.1.1.tgz", - "integrity": "sha512-h882zE4NpfXQIzCKq6cXq4FBTd43rLCLX5RZL/sa38cFVNDp51HNn+rU9l4PeXQOKllq4CVmj9ePgVecyMpr2Q==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-21.1.2.tgz", + "integrity": "sha512-i/FTbqVwj0Wk6B5RA2H9iVsDC/kIK/5koSEwkIQjXGZuDVFUoEuWiIR2PGGSSQ9u3DmkpVPZmKEXWRl+g7Qn5g==", "dev": true, "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2101.1", - "@angular-devkit/build-webpack": "0.2101.1", - "@angular-devkit/core": "21.1.1", - "@angular/build": "21.1.1", + "@angular-devkit/architect": "0.2101.2", + "@angular-devkit/build-webpack": "0.2101.2", + "@angular-devkit/core": "21.1.2", + "@angular/build": "21.1.2", "@babel/core": "7.28.5", "@babel/generator": "7.28.5", "@babel/helper-annotate-as-pure": "7.27.3", @@ -667,7 +667,7 @@ "@babel/preset-env": "7.28.5", "@babel/runtime": "7.28.4", "@discoveryjs/json-ext": "0.6.3", - "@ngtools/webpack": "21.1.1", + "@ngtools/webpack": "21.1.2", "ansi-colors": "4.1.3", "autoprefixer": "10.4.23", "babel-loader": "10.0.0", @@ -722,7 +722,7 @@ "@angular/platform-browser": "^21.0.0", "@angular/platform-server": "^21.0.0", "@angular/service-worker": "^21.0.0", - "@angular/ssr": "^21.1.1", + "@angular/ssr": "^21.1.2", "@web/test-runner": "^0.20.0", "browser-sync": "^3.0.2", "jest": "^30.2.0", @@ -779,12 +779,12 @@ } }, "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/architect": { - "version": "0.2101.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.1.tgz", - "integrity": "sha512-8x7hKcFs3hnpDaIj9fyzinh4X74oQaMxMsZzBf4dBL7EwokjPIf2fadQsZd8a5M+Ja4tIgTnXH9ySyaRFWGNXA==", + "version": "0.2101.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.2.tgz", + "integrity": "sha512-pV2onJgp16xO0vAqEfRWVynRPPLVHydYLANNa3UX3l5T39JcYdMIoOHSIIl8tWrxVeOwiWd1ajub0VsFTUok4Q==", "dev": true, "dependencies": { - "@angular-devkit/core": "21.1.1", + "@angular-devkit/core": "21.1.2", "rxjs": "7.8.2" }, "bin": { @@ -797,9 +797,9 @@ } }, "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.1.tgz", - "integrity": "sha512-rCwfBUemyRoAfrO4c85b49lkPiD5WljWE+IK7vjUNIFFf4TXOS4tg4zxqopUDVE4zEjXORa5oHCEc5HCerjn1g==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.2.tgz", + "integrity": "sha512-0wl5nJlFWsbwfUB2CQeTSmnVQ8AtqqwM3bYPYtXSc+vA8+hzsOAjjDuRnBxZS9zTnqtXKXB1e7M3Iy7KUwh7LA==", "dev": true, "dependencies": { "ajv": "8.17.1", @@ -1045,12 +1045,12 @@ } }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.2101.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.2101.1.tgz", - "integrity": "sha512-gX5/4RT/1ZO6kyo6bEi8uSxZ5oqdolsi87PchKRJfFir2m8u101qs3H07o4KFgG4YlnPUwyHET3ae5YVhS/0xg==", + "version": "0.2101.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.2101.2.tgz", + "integrity": "sha512-/rC9rcrG+Tn8MZIEW9LTHmBuLiQdCHZyscgqgMXD049qgB858gS1Y/lP/tt0xrP3Yhan5XNcRYjcv6sYPtmPUw==", "dev": true, "dependencies": { - "@angular-devkit/architect": "0.2101.1", + "@angular-devkit/architect": "0.2101.2", "rxjs": "7.8.2" }, "engines": { @@ -1064,12 +1064,12 @@ } }, "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/architect": { - "version": "0.2101.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.1.tgz", - "integrity": "sha512-8x7hKcFs3hnpDaIj9fyzinh4X74oQaMxMsZzBf4dBL7EwokjPIf2fadQsZd8a5M+Ja4tIgTnXH9ySyaRFWGNXA==", + "version": "0.2101.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.2.tgz", + "integrity": "sha512-pV2onJgp16xO0vAqEfRWVynRPPLVHydYLANNa3UX3l5T39JcYdMIoOHSIIl8tWrxVeOwiWd1ajub0VsFTUok4Q==", "dev": true, "dependencies": { - "@angular-devkit/core": "21.1.1", + "@angular-devkit/core": "21.1.2", "rxjs": "7.8.2" }, "bin": { @@ -1082,9 +1082,9 @@ } }, "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/core": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.1.tgz", - "integrity": "sha512-rCwfBUemyRoAfrO4c85b49lkPiD5WljWE+IK7vjUNIFFf4TXOS4tg4zxqopUDVE4zEjXORa5oHCEc5HCerjn1g==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.2.tgz", + "integrity": "sha512-0wl5nJlFWsbwfUB2CQeTSmnVQ8AtqqwM3bYPYtXSc+vA8+hzsOAjjDuRnBxZS9zTnqtXKXB1e7M3Iy7KUwh7LA==", "dev": true, "dependencies": { "ajv": "8.17.1", @@ -1372,9 +1372,9 @@ "license": "MIT" }, "node_modules/@angular/animations": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-21.1.1.tgz", - "integrity": "sha512-OQRyNbFBCkuihdCegrpN/Np5YQ7uV9if48LAoXxT68tYhK3S/Qbyx2MzJpOMFEFNfpjXRg1BZr8hVcZVFnArpg==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-21.1.2.tgz", + "integrity": "sha512-8lVSH3y/Pq22ND9ng80UQwQRiIPIE7oD3vuV98Wufld59+s5g4PdJNqPhEVD5dkYD0gYQcm3jTIXSeYuOfpsUg==", "dependencies": { "tslib": "^2.3.0" }, @@ -1382,17 +1382,17 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/core": "21.1.1" + "@angular/core": "21.1.2" } }, "node_modules/@angular/build": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-21.1.1.tgz", - "integrity": "sha512-OqlfH7tkahw/lFT6ACU6mqt3AGgTxxT27JTqpzZOeGo1ferR9dq1O6/CT4GiNyr/Z1AMfs7rBWlQH68y1QZb2g==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-21.1.2.tgz", + "integrity": "sha512-5hl7OTZeQcdkr/3LXSijLuUCwlcqGyYJYOb8GbFqSifSR03JFI3tLQtyQ0LX2CXv3MOx1qFUQbYVfcjW5M36QQ==", "dev": true, "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2101.1", + "@angular-devkit/architect": "0.2101.2", "@babel/core": "7.28.5", "@babel/helper-annotate-as-pure": "7.27.3", "@babel/helper-split-export-declaration": "7.24.7", @@ -1435,7 +1435,7 @@ "@angular/platform-browser": "^21.0.0", "@angular/platform-server": "^21.0.0", "@angular/service-worker": "^21.0.0", - "@angular/ssr": "^21.1.1", + "@angular/ssr": "^21.1.2", "karma": "^6.4.0", "less": "^4.2.0", "ng-packagr": "^21.0.0", @@ -1485,12 +1485,12 @@ } }, "node_modules/@angular/build/node_modules/@angular-devkit/architect": { - "version": "0.2101.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.1.tgz", - "integrity": "sha512-8x7hKcFs3hnpDaIj9fyzinh4X74oQaMxMsZzBf4dBL7EwokjPIf2fadQsZd8a5M+Ja4tIgTnXH9ySyaRFWGNXA==", + "version": "0.2101.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.2.tgz", + "integrity": "sha512-pV2onJgp16xO0vAqEfRWVynRPPLVHydYLANNa3UX3l5T39JcYdMIoOHSIIl8tWrxVeOwiWd1ajub0VsFTUok4Q==", "dev": true, "dependencies": { - "@angular-devkit/core": "21.1.1", + "@angular-devkit/core": "21.1.2", "rxjs": "7.8.2" }, "bin": { @@ -1503,9 +1503,9 @@ } }, "node_modules/@angular/build/node_modules/@angular-devkit/core": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.1.tgz", - "integrity": "sha512-rCwfBUemyRoAfrO4c85b49lkPiD5WljWE+IK7vjUNIFFf4TXOS4tg4zxqopUDVE4zEjXORa5oHCEc5HCerjn1g==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.2.tgz", + "integrity": "sha512-0wl5nJlFWsbwfUB2CQeTSmnVQ8AtqqwM3bYPYtXSc+vA8+hzsOAjjDuRnBxZS9zTnqtXKXB1e7M3Iy7KUwh7LA==", "dev": true, "dependencies": { "ajv": "8.17.1", @@ -1586,9 +1586,9 @@ } }, "node_modules/@angular/cdk": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-21.1.1.tgz", - "integrity": "sha512-lzscv+A6FCQdyWIr0t0QHXEgkLzS9wJwgeOOOhtxbixxxuk7xVXdcK/jnswE1Maugh1m696jUkOhZpffks3psA==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-21.1.2.tgz", + "integrity": "sha512-0q+PhBKmjKO0Yi353VCpMxT0g787cllLhdpyxh00i3twxNWvFkQZgy2Ih187ZXydvW+u9mFkK9+UGLzncQ0yng==", "dependencies": { "parse5": "^8.0.0", "tslib": "^2.3.0" @@ -1625,17 +1625,17 @@ } }, "node_modules/@angular/cli": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-21.1.1.tgz", - "integrity": "sha512-eXhHuYvruWHBn7lX3GuAyLq29+ELwPADOW8ShzZkWRPNlIDiFDsS5pXrxkM9ez+8f86kfDHh88Twevn4UBUqQg==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-21.1.2.tgz", + "integrity": "sha512-AHjXCBl2PEilMJct6DX3ih5Fl5PiKpNDIj0ViTyVh1YcfpYjt6NzhVlV2o++8VNPNH/vMcmf2551LZIDProXXA==", "dependencies": { - "@angular-devkit/architect": "0.2101.1", - "@angular-devkit/core": "21.1.1", - "@angular-devkit/schematics": "21.1.1", + "@angular-devkit/architect": "0.2101.2", + "@angular-devkit/core": "21.1.2", + "@angular-devkit/schematics": "21.1.2", "@inquirer/prompts": "7.10.1", "@listr2/prompt-adapter-inquirer": "3.0.5", "@modelcontextprotocol/sdk": "1.25.2", - "@schematics/angular": "21.1.1", + "@schematics/angular": "21.1.2", "@yarnpkg/lockfile": "1.1.0", "algoliasearch": "5.46.2", "ini": "6.0.0", @@ -1659,11 +1659,11 @@ } }, "node_modules/@angular/cli/node_modules/@angular-devkit/architect": { - "version": "0.2101.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.1.tgz", - "integrity": "sha512-8x7hKcFs3hnpDaIj9fyzinh4X74oQaMxMsZzBf4dBL7EwokjPIf2fadQsZd8a5M+Ja4tIgTnXH9ySyaRFWGNXA==", + "version": "0.2101.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.2.tgz", + "integrity": "sha512-pV2onJgp16xO0vAqEfRWVynRPPLVHydYLANNa3UX3l5T39JcYdMIoOHSIIl8tWrxVeOwiWd1ajub0VsFTUok4Q==", "dependencies": { - "@angular-devkit/core": "21.1.1", + "@angular-devkit/core": "21.1.2", "rxjs": "7.8.2" }, "bin": { @@ -1676,9 +1676,9 @@ } }, "node_modules/@angular/cli/node_modules/@angular-devkit/core": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.1.tgz", - "integrity": "sha512-rCwfBUemyRoAfrO4c85b49lkPiD5WljWE+IK7vjUNIFFf4TXOS4tg4zxqopUDVE4zEjXORa5oHCEc5HCerjn1g==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.2.tgz", + "integrity": "sha512-0wl5nJlFWsbwfUB2CQeTSmnVQ8AtqqwM3bYPYtXSc+vA8+hzsOAjjDuRnBxZS9zTnqtXKXB1e7M3Iy7KUwh7LA==", "dependencies": { "ajv": "8.17.1", "ajv-formats": "3.0.1", @@ -1702,11 +1702,11 @@ } }, "node_modules/@angular/cli/node_modules/@angular-devkit/schematics": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.1.1.tgz", - "integrity": "sha512-3ptEOuALghEYEPVbhRa7g8a+YmvmHqHVNqF9XqCbG22nPGWkE58qfNNbXi3tF9iQxzKSGw5Iy5gYUvSvpsdcfw==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.1.2.tgz", + "integrity": "sha512-PA3gkiFhHUuXd2XuP7yzKg/9N++bjw+uOl473KwIsMuZwMPhncKa4+mUYBaffDoPqaujZvjfo6mjtCBuiBv05w==", "dependencies": { - "@angular-devkit/core": "21.1.1", + "@angular-devkit/core": "21.1.2", "jsonc-parser": "3.3.1", "magic-string": "0.30.21", "ora": "9.0.0", @@ -1846,9 +1846,9 @@ } }, "node_modules/@angular/cli/node_modules/ora/node_modules/string-width": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", - "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.1.tgz", + "integrity": "sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==", "dependencies": { "get-east-asian-width": "^1.3.0", "strip-ansi": "^7.1.0" @@ -1947,9 +1947,9 @@ } }, "node_modules/@angular/common": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-21.1.1.tgz", - "integrity": "sha512-Di2I6TooHdKun3SqRr45o4LbWJq/ZdwUt3fg0X3obPYaP/f6TrFQ4TMjcl03EfPufPtoQx6O+d32rcWVLhDxyw==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-21.1.2.tgz", + "integrity": "sha512-NK26OG1+/3EXLDWstSPmdGbkpt8bP9AsT9J7EBornMswUjmQDbjyb85N/esKjRjDMkw4p/aKpBo24eCV5uUmBA==", "dependencies": { "tslib": "^2.3.0" }, @@ -1957,14 +1957,14 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/core": "21.1.1", + "@angular/core": "21.1.2", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/compiler": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-21.1.1.tgz", - "integrity": "sha512-Urd3bh0zv0MQ//S7RRTanIkOMAZH/A7vSMXUDJ3aflplNs7JNbVqBwDNj8NoX1V+os+fd8JRJOReCc1EpH4ZKQ==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-21.1.2.tgz", + "integrity": "sha512-5OFdZPNix7iK4HSdRxPgg74VvcmQZAMzv9ACYZ8iGfNxiJUjFSurfz0AtVEh0oE2oZDH1v48bHI1s+0ljCHZhA==", "dependencies": { "tslib": "^2.3.0" }, @@ -1973,9 +1973,9 @@ } }, "node_modules/@angular/compiler-cli": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-21.1.1.tgz", - "integrity": "sha512-CCB8SZS0BzqLOdOaMpPpOW256msuatYCFDRTaT+awYIY1vQp/eLXzkMTD2uqyHraQy8cReeH/P6optRP9A077Q==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-21.1.2.tgz", + "integrity": "sha512-h+sX7QvSz58KvmRwNMa33EZHti8Cnw1DL01kInJ/foDchC/O2VMOumeGHS+lAe48t2Nbhiq/obgf275TkDZYsA==", "dependencies": { "@babel/core": "7.28.5", "@jridgewell/sourcemap-codec": "^1.4.14", @@ -1994,7 +1994,7 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/compiler": "21.1.1", + "@angular/compiler": "21.1.2", "typescript": ">=5.9 <6.0" }, "peerDependenciesMeta": { @@ -2151,9 +2151,9 @@ } }, "node_modules/@angular/core": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-21.1.1.tgz", - "integrity": "sha512-KFRCEhsi02pY1EqJ5rnze4mzSaacqh14D8goDhtmARiUH0tefaHR+uKyu4bKSrWga2T/ExG0DJX52LhHRs2qSw==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-21.1.2.tgz", + "integrity": "sha512-W2xxRb7noOD1DdMwKaZ3chFhii6nutaNIXt7dfWsMWoujg3Kqpdn1ukeyW5aHKQZvCJTIGr4f3whZ8Sj/17aCA==", "dependencies": { "tslib": "^2.3.0" }, @@ -2161,7 +2161,7 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/compiler": "21.1.1", + "@angular/compiler": "21.1.2", "rxjs": "^6.5.3 || ^7.4.0", "zone.js": "~0.15.0 || ~0.16.0" }, @@ -2175,9 +2175,9 @@ } }, "node_modules/@angular/elements": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular/elements/-/elements-21.1.1.tgz", - "integrity": "sha512-2ROobfnYWxAZlDKB3lYdo6V7utX96d43HRX3hU0BG5T6gglBwNnvDGClpmxOqwtP/uhf1fk+BVSSsjtiUWX3vg==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/elements/-/elements-21.1.2.tgz", + "integrity": "sha512-x8RpuQHYVGKF5VuhRR/7ndeGS1vFt8r8PtkPaR1MobCxQkTr0MGfyXOB8wTrA/pvgXf2Yqv3apFyfNILnm9YrQ==", "dependencies": { "tslib": "^2.3.0" }, @@ -2185,14 +2185,14 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/core": "21.1.1", + "@angular/core": "21.1.2", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/forms": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-21.1.1.tgz", - "integrity": "sha512-NBbJOynLOeMsPo03+3dfdxE0P7SB7SXRqoFJ7WP2sOgOIxODna/huo2blmRlnZAVPTn1iQEB9Q+UeyP5c4/1+w==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-21.1.2.tgz", + "integrity": "sha512-dY56FuoBEvfLMtatKGg1vMFSwgySzWJm3URaBj3GpFTjhnuByHoxH4Lb5u50lrrVc9VQt/BZmq3mDZXjlx6Qgw==", "dependencies": { "@standard-schema/spec": "^1.0.0", "tslib": "^2.3.0" @@ -2201,25 +2201,25 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "21.1.1", - "@angular/core": "21.1.1", - "@angular/platform-browser": "21.1.1", + "@angular/common": "21.1.2", + "@angular/core": "21.1.2", + "@angular/platform-browser": "21.1.2", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/language-service": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-21.1.1.tgz", - "integrity": "sha512-Nniqe8X5mTIm37u46HDXCEDuYIv+G5nJZuz1BwuSyDgqxCmdJ3asdgkxgkRQW8NUjXmj6/2vWJ3gn/by4VcKEA==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-21.1.2.tgz", + "integrity": "sha512-/2VXz08k0BVQoYiDv/AyQgDY9AVzFuo29I/OAh28za58ReiXkT/WOWgP4el1rewX4uxWnM+BEpYxC3hcc+Ls0Q==", "dev": true, "engines": { "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/@angular/platform-browser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-21.1.1.tgz", - "integrity": "sha512-d6liZjPz29GUZ6dhxytFL/W2nMsYwPpc/E/vZpr5yV+u+gI2VjbnLbl8SG+jjj0/Hyq7s4aGhEKsRrCJJMXgNw==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-21.1.2.tgz", + "integrity": "sha512-8vnCbQhxugQ3meGQ0YlSp0uNBYUjpFXYjFnGQ0Xq5jvzc9WX7KSix6+AydEjZtQfc1bWRetBTOlhQpqnwYp53g==", "dependencies": { "tslib": "^2.3.0" }, @@ -2227,9 +2227,9 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/animations": "21.1.1", - "@angular/common": "21.1.1", - "@angular/core": "21.1.1" + "@angular/animations": "21.1.2", + "@angular/common": "21.1.2", + "@angular/core": "21.1.2" }, "peerDependenciesMeta": { "@angular/animations": { @@ -2238,9 +2238,9 @@ } }, "node_modules/@angular/platform-browser-dynamic": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-21.1.1.tgz", - "integrity": "sha512-lawT3bdjXZVmVNXVoPS0UiB8Qxw5jEYXHx2m38JvHGv7/pl0Sgr+wa6f+/4pvTwu3VZb/8ohkVdFicPfrU21Jw==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-21.1.2.tgz", + "integrity": "sha512-3+6Le0CuEpJFdJniD2ol6i9i7gmlJv+Qck5lxY+eHq2Ylj0VJ9sBIFaMBCmvdb6lz7QYnKoZr+Lhv1MX6hVXyg==", "dependencies": { "tslib": "^2.3.0" }, @@ -2248,16 +2248,16 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "21.1.1", - "@angular/compiler": "21.1.1", - "@angular/core": "21.1.1", - "@angular/platform-browser": "21.1.1" + "@angular/common": "21.1.2", + "@angular/compiler": "21.1.2", + "@angular/core": "21.1.2", + "@angular/platform-browser": "21.1.2" } }, "node_modules/@angular/router": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-21.1.1.tgz", - "integrity": "sha512-3ypbtH3KfzuVgebdEET9+bRwn1VzP//KI0tIqleCGi4rblP3WQ/HwIGa5Qhdcxmw/kbmABKLRXX2kRUvidKs/Q==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-21.1.2.tgz", + "integrity": "sha512-APl4tkTJIrpejlULLrGtIdLuJkNctPy0pnVijrJLR52nEV0xX165ulXk3XrL9QnMk0iy950aTYtoTal4aMw16Q==", "dependencies": { "tslib": "^2.3.0" }, @@ -2265,9 +2265,9 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "21.1.1", - "@angular/core": "21.1.1", - "@angular/platform-browser": "21.1.1", + "@angular/common": "21.1.2", + "@angular/core": "21.1.2", + "@angular/platform-browser": "21.1.2", "rxjs": "^6.5.3 || ^7.4.0" } }, @@ -7120,9 +7120,9 @@ } }, "node_modules/@ngtools/webpack": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-21.1.1.tgz", - "integrity": "sha512-8ySRsb1xgr+7XQmZ2LJ+AhFe1IZKW93wfL6OMpZtcWU4FzxWa/NhlfSNBQI5kuyPEVDDAxJ4RI5IoQyvcOmNLg==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-21.1.2.tgz", + "integrity": "sha512-ZNMMD35urDKqYtx1drxPyGAvUPMOoiKjvrH8owpN+mzIO1nYpssCgmAseo1hePAduSvv8tAsY1NLtJfMSNzubw==", "dev": true, "engines": { "node": "^20.19.0 || ^22.12.0 || >=24.0.0", @@ -8419,12 +8419,12 @@ "dev": true }, "node_modules/@schematics/angular": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-21.1.1.tgz", - "integrity": "sha512-WijqITteakpFOplx7IGHIdBOdTU04Ul4qweilY1CRK3KdzQRuAf31KiKUFrJiGW076cyokmAQmBoZcngh9rCNw==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-21.1.2.tgz", + "integrity": "sha512-kxwxhCIUrj7DfzEtDSs/pi/w+aII/WQLpPfLgoQCWE8/95v60WnTfd1afmsXsFoxikKPxkwoPWtU2YbhSoX9MQ==", "dependencies": { - "@angular-devkit/core": "21.1.1", - "@angular-devkit/schematics": "21.1.1", + "@angular-devkit/core": "21.1.2", + "@angular-devkit/schematics": "21.1.2", "jsonc-parser": "3.3.1" }, "engines": { @@ -8434,9 +8434,9 @@ } }, "node_modules/@schematics/angular/node_modules/@angular-devkit/core": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.1.tgz", - "integrity": "sha512-rCwfBUemyRoAfrO4c85b49lkPiD5WljWE+IK7vjUNIFFf4TXOS4tg4zxqopUDVE4zEjXORa5oHCEc5HCerjn1g==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.2.tgz", + "integrity": "sha512-0wl5nJlFWsbwfUB2CQeTSmnVQ8AtqqwM3bYPYtXSc+vA8+hzsOAjjDuRnBxZS9zTnqtXKXB1e7M3Iy7KUwh7LA==", "dependencies": { "ajv": "8.17.1", "ajv-formats": "3.0.1", @@ -8460,11 +8460,11 @@ } }, "node_modules/@schematics/angular/node_modules/@angular-devkit/schematics": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.1.1.tgz", - "integrity": "sha512-3ptEOuALghEYEPVbhRa7g8a+YmvmHqHVNqF9XqCbG22nPGWkE58qfNNbXi3tF9iQxzKSGw5Iy5gYUvSvpsdcfw==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.1.2.tgz", + "integrity": "sha512-PA3gkiFhHUuXd2XuP7yzKg/9N++bjw+uOl473KwIsMuZwMPhncKa4+mUYBaffDoPqaujZvjfo6mjtCBuiBv05w==", "dependencies": { - "@angular-devkit/core": "21.1.1", + "@angular-devkit/core": "21.1.2", "jsonc-parser": "3.3.1", "magic-string": "0.30.21", "ora": "9.0.0", @@ -8582,9 +8582,9 @@ } }, "node_modules/@schematics/angular/node_modules/string-width": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", - "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.1.tgz", + "integrity": "sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==", "dependencies": { "get-east-asian-width": "^1.3.0", "strip-ansi": "^7.1.0" @@ -26135,16 +26135,16 @@ } }, "@angular-devkit/build-angular": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-21.1.1.tgz", - "integrity": "sha512-h882zE4NpfXQIzCKq6cXq4FBTd43rLCLX5RZL/sa38cFVNDp51HNn+rU9l4PeXQOKllq4CVmj9ePgVecyMpr2Q==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-21.1.2.tgz", + "integrity": "sha512-i/FTbqVwj0Wk6B5RA2H9iVsDC/kIK/5koSEwkIQjXGZuDVFUoEuWiIR2PGGSSQ9u3DmkpVPZmKEXWRl+g7Qn5g==", "dev": true, "requires": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2101.1", - "@angular-devkit/build-webpack": "0.2101.1", - "@angular-devkit/core": "21.1.1", - "@angular/build": "21.1.1", + "@angular-devkit/architect": "0.2101.2", + "@angular-devkit/build-webpack": "0.2101.2", + "@angular-devkit/core": "21.1.2", + "@angular/build": "21.1.2", "@babel/core": "7.28.5", "@babel/generator": "7.28.5", "@babel/helper-annotate-as-pure": "7.27.3", @@ -26155,7 +26155,7 @@ "@babel/preset-env": "7.28.5", "@babel/runtime": "7.28.4", "@discoveryjs/json-ext": "0.6.3", - "@ngtools/webpack": "21.1.1", + "@ngtools/webpack": "21.1.2", "ansi-colors": "4.1.3", "autoprefixer": "10.4.23", "babel-loader": "10.0.0", @@ -26198,19 +26198,19 @@ }, "dependencies": { "@angular-devkit/architect": { - "version": "0.2101.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.1.tgz", - "integrity": "sha512-8x7hKcFs3hnpDaIj9fyzinh4X74oQaMxMsZzBf4dBL7EwokjPIf2fadQsZd8a5M+Ja4tIgTnXH9ySyaRFWGNXA==", + "version": "0.2101.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.2.tgz", + "integrity": "sha512-pV2onJgp16xO0vAqEfRWVynRPPLVHydYLANNa3UX3l5T39JcYdMIoOHSIIl8tWrxVeOwiWd1ajub0VsFTUok4Q==", "dev": true, "requires": { - "@angular-devkit/core": "21.1.1", + "@angular-devkit/core": "21.1.2", "rxjs": "7.8.2" } }, "@angular-devkit/core": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.1.tgz", - "integrity": "sha512-rCwfBUemyRoAfrO4c85b49lkPiD5WljWE+IK7vjUNIFFf4TXOS4tg4zxqopUDVE4zEjXORa5oHCEc5HCerjn1g==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.2.tgz", + "integrity": "sha512-0wl5nJlFWsbwfUB2CQeTSmnVQ8AtqqwM3bYPYtXSc+vA8+hzsOAjjDuRnBxZS9zTnqtXKXB1e7M3Iy7KUwh7LA==", "dev": true, "requires": { "ajv": "8.17.1", @@ -26355,29 +26355,29 @@ } }, "@angular-devkit/build-webpack": { - "version": "0.2101.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.2101.1.tgz", - "integrity": "sha512-gX5/4RT/1ZO6kyo6bEi8uSxZ5oqdolsi87PchKRJfFir2m8u101qs3H07o4KFgG4YlnPUwyHET3ae5YVhS/0xg==", + "version": "0.2101.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.2101.2.tgz", + "integrity": "sha512-/rC9rcrG+Tn8MZIEW9LTHmBuLiQdCHZyscgqgMXD049qgB858gS1Y/lP/tt0xrP3Yhan5XNcRYjcv6sYPtmPUw==", "dev": true, "requires": { - "@angular-devkit/architect": "0.2101.1", + "@angular-devkit/architect": "0.2101.2", "rxjs": "7.8.2" }, "dependencies": { "@angular-devkit/architect": { - "version": "0.2101.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.1.tgz", - "integrity": "sha512-8x7hKcFs3hnpDaIj9fyzinh4X74oQaMxMsZzBf4dBL7EwokjPIf2fadQsZd8a5M+Ja4tIgTnXH9ySyaRFWGNXA==", + "version": "0.2101.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.2.tgz", + "integrity": "sha512-pV2onJgp16xO0vAqEfRWVynRPPLVHydYLANNa3UX3l5T39JcYdMIoOHSIIl8tWrxVeOwiWd1ajub0VsFTUok4Q==", "dev": true, "requires": { - "@angular-devkit/core": "21.1.1", + "@angular-devkit/core": "21.1.2", "rxjs": "7.8.2" } }, "@angular-devkit/core": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.1.tgz", - "integrity": "sha512-rCwfBUemyRoAfrO4c85b49lkPiD5WljWE+IK7vjUNIFFf4TXOS4tg4zxqopUDVE4zEjXORa5oHCEc5HCerjn1g==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.2.tgz", + "integrity": "sha512-0wl5nJlFWsbwfUB2CQeTSmnVQ8AtqqwM3bYPYtXSc+vA8+hzsOAjjDuRnBxZS9zTnqtXKXB1e7M3Iy7KUwh7LA==", "dev": true, "requires": { "ajv": "8.17.1", @@ -26573,21 +26573,21 @@ } }, "@angular/animations": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-21.1.1.tgz", - "integrity": "sha512-OQRyNbFBCkuihdCegrpN/Np5YQ7uV9if48LAoXxT68tYhK3S/Qbyx2MzJpOMFEFNfpjXRg1BZr8hVcZVFnArpg==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-21.1.2.tgz", + "integrity": "sha512-8lVSH3y/Pq22ND9ng80UQwQRiIPIE7oD3vuV98Wufld59+s5g4PdJNqPhEVD5dkYD0gYQcm3jTIXSeYuOfpsUg==", "requires": { "tslib": "^2.3.0" } }, "@angular/build": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-21.1.1.tgz", - "integrity": "sha512-OqlfH7tkahw/lFT6ACU6mqt3AGgTxxT27JTqpzZOeGo1ferR9dq1O6/CT4GiNyr/Z1AMfs7rBWlQH68y1QZb2g==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-21.1.2.tgz", + "integrity": "sha512-5hl7OTZeQcdkr/3LXSijLuUCwlcqGyYJYOb8GbFqSifSR03JFI3tLQtyQ0LX2CXv3MOx1qFUQbYVfcjW5M36QQ==", "dev": true, "requires": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2101.1", + "@angular-devkit/architect": "0.2101.2", "@babel/core": "7.28.5", "@babel/helper-annotate-as-pure": "7.27.3", "@babel/helper-split-export-declaration": "7.24.7", @@ -26617,19 +26617,19 @@ }, "dependencies": { "@angular-devkit/architect": { - "version": "0.2101.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.1.tgz", - "integrity": "sha512-8x7hKcFs3hnpDaIj9fyzinh4X74oQaMxMsZzBf4dBL7EwokjPIf2fadQsZd8a5M+Ja4tIgTnXH9ySyaRFWGNXA==", + "version": "0.2101.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.2.tgz", + "integrity": "sha512-pV2onJgp16xO0vAqEfRWVynRPPLVHydYLANNa3UX3l5T39JcYdMIoOHSIIl8tWrxVeOwiWd1ajub0VsFTUok4Q==", "dev": true, "requires": { - "@angular-devkit/core": "21.1.1", + "@angular-devkit/core": "21.1.2", "rxjs": "7.8.2" } }, "@angular-devkit/core": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.1.tgz", - "integrity": "sha512-rCwfBUemyRoAfrO4c85b49lkPiD5WljWE+IK7vjUNIFFf4TXOS4tg4zxqopUDVE4zEjXORa5oHCEc5HCerjn1g==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.2.tgz", + "integrity": "sha512-0wl5nJlFWsbwfUB2CQeTSmnVQ8AtqqwM3bYPYtXSc+vA8+hzsOAjjDuRnBxZS9zTnqtXKXB1e7M3Iy7KUwh7LA==", "dev": true, "requires": { "ajv": "8.17.1", @@ -26680,9 +26680,9 @@ } }, "@angular/cdk": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-21.1.1.tgz", - "integrity": "sha512-lzscv+A6FCQdyWIr0t0QHXEgkLzS9wJwgeOOOhtxbixxxuk7xVXdcK/jnswE1Maugh1m696jUkOhZpffks3psA==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-21.1.2.tgz", + "integrity": "sha512-0q+PhBKmjKO0Yi353VCpMxT0g787cllLhdpyxh00i3twxNWvFkQZgy2Ih187ZXydvW+u9mFkK9+UGLzncQ0yng==", "requires": { "parse5": "^8.0.0", "tslib": "^2.3.0" @@ -26704,17 +26704,17 @@ } }, "@angular/cli": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-21.1.1.tgz", - "integrity": "sha512-eXhHuYvruWHBn7lX3GuAyLq29+ELwPADOW8ShzZkWRPNlIDiFDsS5pXrxkM9ez+8f86kfDHh88Twevn4UBUqQg==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-21.1.2.tgz", + "integrity": "sha512-AHjXCBl2PEilMJct6DX3ih5Fl5PiKpNDIj0ViTyVh1YcfpYjt6NzhVlV2o++8VNPNH/vMcmf2551LZIDProXXA==", "requires": { - "@angular-devkit/architect": "0.2101.1", - "@angular-devkit/core": "21.1.1", - "@angular-devkit/schematics": "21.1.1", + "@angular-devkit/architect": "0.2101.2", + "@angular-devkit/core": "21.1.2", + "@angular-devkit/schematics": "21.1.2", "@inquirer/prompts": "7.10.1", "@listr2/prompt-adapter-inquirer": "3.0.5", "@modelcontextprotocol/sdk": "1.25.2", - "@schematics/angular": "21.1.1", + "@schematics/angular": "21.1.2", "@yarnpkg/lockfile": "1.1.0", "algoliasearch": "5.46.2", "ini": "6.0.0", @@ -26730,18 +26730,18 @@ }, "dependencies": { "@angular-devkit/architect": { - "version": "0.2101.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.1.tgz", - "integrity": "sha512-8x7hKcFs3hnpDaIj9fyzinh4X74oQaMxMsZzBf4dBL7EwokjPIf2fadQsZd8a5M+Ja4tIgTnXH9ySyaRFWGNXA==", + "version": "0.2101.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.2.tgz", + "integrity": "sha512-pV2onJgp16xO0vAqEfRWVynRPPLVHydYLANNa3UX3l5T39JcYdMIoOHSIIl8tWrxVeOwiWd1ajub0VsFTUok4Q==", "requires": { - "@angular-devkit/core": "21.1.1", + "@angular-devkit/core": "21.1.2", "rxjs": "7.8.2" } }, "@angular-devkit/core": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.1.tgz", - "integrity": "sha512-rCwfBUemyRoAfrO4c85b49lkPiD5WljWE+IK7vjUNIFFf4TXOS4tg4zxqopUDVE4zEjXORa5oHCEc5HCerjn1g==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.2.tgz", + "integrity": "sha512-0wl5nJlFWsbwfUB2CQeTSmnVQ8AtqqwM3bYPYtXSc+vA8+hzsOAjjDuRnBxZS9zTnqtXKXB1e7M3Iy7KUwh7LA==", "requires": { "ajv": "8.17.1", "ajv-formats": "3.0.1", @@ -26752,11 +26752,11 @@ } }, "@angular-devkit/schematics": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.1.1.tgz", - "integrity": "sha512-3ptEOuALghEYEPVbhRa7g8a+YmvmHqHVNqF9XqCbG22nPGWkE58qfNNbXi3tF9iQxzKSGw5Iy5gYUvSvpsdcfw==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.1.2.tgz", + "integrity": "sha512-PA3gkiFhHUuXd2XuP7yzKg/9N++bjw+uOl473KwIsMuZwMPhncKa4+mUYBaffDoPqaujZvjfo6mjtCBuiBv05w==", "requires": { - "@angular-devkit/core": "21.1.1", + "@angular-devkit/core": "21.1.2", "jsonc-parser": "3.3.1", "magic-string": "0.30.21", "ora": "9.0.0", @@ -26840,9 +26840,9 @@ }, "dependencies": { "string-width": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", - "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.1.tgz", + "integrity": "sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==", "requires": { "get-east-asian-width": "^1.3.0", "strip-ansi": "^7.1.0" @@ -26904,25 +26904,25 @@ } }, "@angular/common": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-21.1.1.tgz", - "integrity": "sha512-Di2I6TooHdKun3SqRr45o4LbWJq/ZdwUt3fg0X3obPYaP/f6TrFQ4TMjcl03EfPufPtoQx6O+d32rcWVLhDxyw==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-21.1.2.tgz", + "integrity": "sha512-NK26OG1+/3EXLDWstSPmdGbkpt8bP9AsT9J7EBornMswUjmQDbjyb85N/esKjRjDMkw4p/aKpBo24eCV5uUmBA==", "requires": { "tslib": "^2.3.0" } }, "@angular/compiler": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-21.1.1.tgz", - "integrity": "sha512-Urd3bh0zv0MQ//S7RRTanIkOMAZH/A7vSMXUDJ3aflplNs7JNbVqBwDNj8NoX1V+os+fd8JRJOReCc1EpH4ZKQ==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-21.1.2.tgz", + "integrity": "sha512-5OFdZPNix7iK4HSdRxPgg74VvcmQZAMzv9ACYZ8iGfNxiJUjFSurfz0AtVEh0oE2oZDH1v48bHI1s+0ljCHZhA==", "requires": { "tslib": "^2.3.0" } }, "@angular/compiler-cli": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-21.1.1.tgz", - "integrity": "sha512-CCB8SZS0BzqLOdOaMpPpOW256msuatYCFDRTaT+awYIY1vQp/eLXzkMTD2uqyHraQy8cReeH/P6optRP9A077Q==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-21.1.2.tgz", + "integrity": "sha512-h+sX7QvSz58KvmRwNMa33EZHti8Cnw1DL01kInJ/foDchC/O2VMOumeGHS+lAe48t2Nbhiq/obgf275TkDZYsA==", "requires": { "@babel/core": "7.28.5", "@jridgewell/sourcemap-codec": "^1.4.14", @@ -27021,56 +27021,56 @@ } }, "@angular/core": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-21.1.1.tgz", - "integrity": "sha512-KFRCEhsi02pY1EqJ5rnze4mzSaacqh14D8goDhtmARiUH0tefaHR+uKyu4bKSrWga2T/ExG0DJX52LhHRs2qSw==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-21.1.2.tgz", + "integrity": "sha512-W2xxRb7noOD1DdMwKaZ3chFhii6nutaNIXt7dfWsMWoujg3Kqpdn1ukeyW5aHKQZvCJTIGr4f3whZ8Sj/17aCA==", "requires": { "tslib": "^2.3.0" } }, "@angular/elements": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular/elements/-/elements-21.1.1.tgz", - "integrity": "sha512-2ROobfnYWxAZlDKB3lYdo6V7utX96d43HRX3hU0BG5T6gglBwNnvDGClpmxOqwtP/uhf1fk+BVSSsjtiUWX3vg==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/elements/-/elements-21.1.2.tgz", + "integrity": "sha512-x8RpuQHYVGKF5VuhRR/7ndeGS1vFt8r8PtkPaR1MobCxQkTr0MGfyXOB8wTrA/pvgXf2Yqv3apFyfNILnm9YrQ==", "requires": { "tslib": "^2.3.0" } }, "@angular/forms": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-21.1.1.tgz", - "integrity": "sha512-NBbJOynLOeMsPo03+3dfdxE0P7SB7SXRqoFJ7WP2sOgOIxODna/huo2blmRlnZAVPTn1iQEB9Q+UeyP5c4/1+w==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-21.1.2.tgz", + "integrity": "sha512-dY56FuoBEvfLMtatKGg1vMFSwgySzWJm3URaBj3GpFTjhnuByHoxH4Lb5u50lrrVc9VQt/BZmq3mDZXjlx6Qgw==", "requires": { "@standard-schema/spec": "^1.0.0", "tslib": "^2.3.0" } }, "@angular/language-service": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-21.1.1.tgz", - "integrity": "sha512-Nniqe8X5mTIm37u46HDXCEDuYIv+G5nJZuz1BwuSyDgqxCmdJ3asdgkxgkRQW8NUjXmj6/2vWJ3gn/by4VcKEA==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-21.1.2.tgz", + "integrity": "sha512-/2VXz08k0BVQoYiDv/AyQgDY9AVzFuo29I/OAh28za58ReiXkT/WOWgP4el1rewX4uxWnM+BEpYxC3hcc+Ls0Q==", "dev": true }, "@angular/platform-browser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-21.1.1.tgz", - "integrity": "sha512-d6liZjPz29GUZ6dhxytFL/W2nMsYwPpc/E/vZpr5yV+u+gI2VjbnLbl8SG+jjj0/Hyq7s4aGhEKsRrCJJMXgNw==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-21.1.2.tgz", + "integrity": "sha512-8vnCbQhxugQ3meGQ0YlSp0uNBYUjpFXYjFnGQ0Xq5jvzc9WX7KSix6+AydEjZtQfc1bWRetBTOlhQpqnwYp53g==", "requires": { "tslib": "^2.3.0" } }, "@angular/platform-browser-dynamic": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-21.1.1.tgz", - "integrity": "sha512-lawT3bdjXZVmVNXVoPS0UiB8Qxw5jEYXHx2m38JvHGv7/pl0Sgr+wa6f+/4pvTwu3VZb/8ohkVdFicPfrU21Jw==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-21.1.2.tgz", + "integrity": "sha512-3+6Le0CuEpJFdJniD2ol6i9i7gmlJv+Qck5lxY+eHq2Ylj0VJ9sBIFaMBCmvdb6lz7QYnKoZr+Lhv1MX6hVXyg==", "requires": { "tslib": "^2.3.0" } }, "@angular/router": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-21.1.1.tgz", - "integrity": "sha512-3ypbtH3KfzuVgebdEET9+bRwn1VzP//KI0tIqleCGi4rblP3WQ/HwIGa5Qhdcxmw/kbmABKLRXX2kRUvidKs/Q==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-21.1.2.tgz", + "integrity": "sha512-APl4tkTJIrpejlULLrGtIdLuJkNctPy0pnVijrJLR52nEV0xX165ulXk3XrL9QnMk0iy950aTYtoTal4aMw16Q==", "requires": { "tslib": "^2.3.0" } @@ -30071,9 +30071,9 @@ } }, "@ngtools/webpack": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-21.1.1.tgz", - "integrity": "sha512-8ySRsb1xgr+7XQmZ2LJ+AhFe1IZKW93wfL6OMpZtcWU4FzxWa/NhlfSNBQI5kuyPEVDDAxJ4RI5IoQyvcOmNLg==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-21.1.2.tgz", + "integrity": "sha512-ZNMMD35urDKqYtx1drxPyGAvUPMOoiKjvrH8owpN+mzIO1nYpssCgmAseo1hePAduSvv8tAsY1NLtJfMSNzubw==", "dev": true }, "@npmcli/agent": { @@ -30765,19 +30765,19 @@ "dev": true }, "@schematics/angular": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-21.1.1.tgz", - "integrity": "sha512-WijqITteakpFOplx7IGHIdBOdTU04Ul4qweilY1CRK3KdzQRuAf31KiKUFrJiGW076cyokmAQmBoZcngh9rCNw==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-21.1.2.tgz", + "integrity": "sha512-kxwxhCIUrj7DfzEtDSs/pi/w+aII/WQLpPfLgoQCWE8/95v60WnTfd1afmsXsFoxikKPxkwoPWtU2YbhSoX9MQ==", "requires": { - "@angular-devkit/core": "21.1.1", - "@angular-devkit/schematics": "21.1.1", + "@angular-devkit/core": "21.1.2", + "@angular-devkit/schematics": "21.1.2", "jsonc-parser": "3.3.1" }, "dependencies": { "@angular-devkit/core": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.1.tgz", - "integrity": "sha512-rCwfBUemyRoAfrO4c85b49lkPiD5WljWE+IK7vjUNIFFf4TXOS4tg4zxqopUDVE4zEjXORa5oHCEc5HCerjn1g==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.2.tgz", + "integrity": "sha512-0wl5nJlFWsbwfUB2CQeTSmnVQ8AtqqwM3bYPYtXSc+vA8+hzsOAjjDuRnBxZS9zTnqtXKXB1e7M3Iy7KUwh7LA==", "requires": { "ajv": "8.17.1", "ajv-formats": "3.0.1", @@ -30788,11 +30788,11 @@ } }, "@angular-devkit/schematics": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.1.1.tgz", - "integrity": "sha512-3ptEOuALghEYEPVbhRa7g8a+YmvmHqHVNqF9XqCbG22nPGWkE58qfNNbXi3tF9iQxzKSGw5Iy5gYUvSvpsdcfw==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.1.2.tgz", + "integrity": "sha512-PA3gkiFhHUuXd2XuP7yzKg/9N++bjw+uOl473KwIsMuZwMPhncKa4+mUYBaffDoPqaujZvjfo6mjtCBuiBv05w==", "requires": { - "@angular-devkit/core": "21.1.1", + "@angular-devkit/core": "21.1.2", "jsonc-parser": "3.3.1", "magic-string": "0.30.21", "ora": "9.0.0", @@ -30861,9 +30861,9 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==" }, "string-width": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", - "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.1.tgz", + "integrity": "sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==", "requires": { "get-east-asian-width": "^1.3.0", "strip-ansi": "^7.1.0" diff --git a/frontend/package.json b/frontend/package.json index abec1e9ebfa..add3fa3e719 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,13 +6,13 @@ "private": true, "devDependencies": { "@angular-builders/custom-esbuild": "^21.0.3", - "@angular-devkit/build-angular": "^21.1.1", + "@angular-devkit/build-angular": "^21.1.2", "@angular-eslint/builder": "20.7.0", "@angular-eslint/eslint-plugin": "20.7.0", "@angular-eslint/eslint-plugin-template": "20.7.0", "@angular-eslint/schematics": "20.7.0", "@angular-eslint/template-parser": "20.7.0", - "@angular/language-service": "21.1.1", + "@angular/language-service": "21.1.2", "@eslint/js": "^9.39.2", "@html-eslint/eslint-plugin": "^0.54.0", "@html-eslint/parser": "^0.54.0", @@ -64,18 +64,18 @@ "wscat": "^6.1.0" }, "dependencies": { - "@angular/animations": "^21.1.1", - "@angular/cdk": "^21.1.1", - "@angular/cli": "^21.1.1", - "@angular/common": "^21.1.1", - "@angular/compiler": "^21.1.1", - "@angular/compiler-cli": "^21.1.1", - "@angular/core": "^21.1.1", - "@angular/elements": "^21.1.1", - "@angular/forms": "^21.1.1", - "@angular/platform-browser": "^21.1.1", - "@angular/platform-browser-dynamic": "^21.1.1", - "@angular/router": "^21.1.1", + "@angular/animations": "^21.1.2", + "@angular/cdk": "^21.1.2", + "@angular/cli": "^21.1.2", + "@angular/common": "^21.1.2", + "@angular/compiler": "^21.1.2", + "@angular/compiler-cli": "^21.1.2", + "@angular/core": "^21.1.2", + "@angular/elements": "^21.1.2", + "@angular/forms": "^21.1.2", + "@angular/platform-browser": "^21.1.2", + "@angular/platform-browser-dynamic": "^21.1.2", + "@angular/router": "^21.1.2", "@appsignal/javascript": "^1.6.1", "@appsignal/plugin-breadcrumbs-console": "^1.1.37", "@appsignal/plugin-breadcrumbs-network": "^1.1.24", From 7a17ee6b52f27b16498d8aa6d7d1700f4d98514a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Thu, 29 Jan 2026 08:50:33 +0100 Subject: [PATCH 020/293] Bumped version to 17.2.0 [ci skip] --- lib/open_project/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/open_project/version.rb b/lib/open_project/version.rb index 55fb32a1e3d..e57217e3845 100644 --- a/lib/open_project/version.rb +++ b/lib/open_project/version.rb @@ -32,7 +32,7 @@ require "open3" module OpenProject module VERSION # :nodoc: MAJOR = 17 - MINOR = 1 + MINOR = 2 PATCH = 0 class << self From bd88659079a6219c034291ca0ebb98fbc53459a8 Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Tue, 20 Jan 2026 15:39:13 +0100 Subject: [PATCH 021/293] First draft of a registry for inline edit components (wip) --- app/components/_index.sass | 1 + .../inplace_edit_field_component.html.erb | 24 ++++++++ .../common/inplace_edit_field_component.rb | 53 ++++++++++++++++++ .../common/inplace_edit_fields/index.sass | 1 + .../text_input_component.rb | 56 +++++++++++++++++++ .../text_input_component.sass | 7 +++ config/initializers/inplace_edit_fields.rb | 34 +++++++++++ lib/open_project/inplace_edit/registry.rb | 45 +++++++++++++++ .../grids/widgets/description.html.erb | 3 +- 9 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 app/components/open_project/common/inplace_edit_field_component.html.erb create mode 100644 app/components/open_project/common/inplace_edit_field_component.rb create mode 100644 app/components/open_project/common/inplace_edit_fields/index.sass create mode 100644 app/components/open_project/common/inplace_edit_fields/text_input_component.rb create mode 100644 app/components/open_project/common/inplace_edit_fields/text_input_component.sass create mode 100644 config/initializers/inplace_edit_fields.rb create mode 100644 lib/open_project/inplace_edit/registry.rb diff --git a/app/components/_index.sass b/app/components/_index.sass index 9265c6cbe8c..589d8517fb8 100644 --- a/app/components/_index.sass +++ b/app/components/_index.sass @@ -7,6 +7,7 @@ @import "open_project/common/attribute_help_text_component" @import "open_project/common/attribute_help_text_caption_component" @import "open_project/common/attribute_label_component" +@import "open_project/common/inplace_edit_fields/index" @import "open_project/common/submenu_component" @import "open_project/common/main_menu_toggle_component" @import "portfolios/details_component" diff --git a/app/components/open_project/common/inplace_edit_field_component.html.erb b/app/components/open_project/common/inplace_edit_field_component.html.erb new file mode 100644 index 00000000000..2a96ab623d9 --- /dev/null +++ b/app/components/open_project/common/inplace_edit_field_component.html.erb @@ -0,0 +1,24 @@ +<%# system_arguments = @system_arguments %> + +<%#= + + + + + + + + + + + %> + +
+ <%= primer_form_with(model:, url: polymorphic_path(model), method: :patch, data: { turbo_stream: true }) do |form| + render_field_component = ->(f) { render field_component(f) } # The render_inline_form method looses context and thus does not know about the `field_component` method + + render_inline_form(form) do |f| + render_field_component.call(f) + end + end %> +
diff --git a/app/components/open_project/common/inplace_edit_field_component.rb b/app/components/open_project/common/inplace_edit_field_component.rb new file mode 100644 index 00000000000..a12590c57d7 --- /dev/null +++ b/app/components/open_project/common/inplace_edit_field_component.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module OpenProject + module Common + class InplaceEditFieldComponent < ViewComponent::Base + attr_reader :model, :attribute + + def initialize(model:, attribute:, **system_arguments) + super() + @model = model + @attribute = attribute + @system_arguments = system_arguments + end + + def field_component(form) + klass = OpenProject::InplaceFieldRegistry.fetch(attribute) + klass.new(form:, attribute:, model:, **@system_arguments) + end + + def target_id + "#{model.model_name.singular}_#{model.id}_#{attribute}" + end + end + end +end diff --git a/app/components/open_project/common/inplace_edit_fields/index.sass b/app/components/open_project/common/inplace_edit_fields/index.sass new file mode 100644 index 00000000000..3543c70e587 --- /dev/null +++ b/app/components/open_project/common/inplace_edit_fields/index.sass @@ -0,0 +1 @@ +@import "text_input_component" diff --git a/app/components/open_project/common/inplace_edit_fields/text_input_component.rb b/app/components/open_project/common/inplace_edit_fields/text_input_component.rb new file mode 100644 index 00000000000..e68feb901ec --- /dev/null +++ b/app/components/open_project/common/inplace_edit_fields/text_input_component.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module OpenProject + module Common + module InplaceEditFields + class TextInputComponent < ViewComponent::Base + attr_reader :form, :attribute, :model + + def initialize(form:, attribute:, model:, **system_arguments) + super() + @form = form + @attribute = attribute + @model = model + @system_arguments = system_arguments + @system_arguments[:classes] = class_names( + @system_arguments[:classes], + "op-inplace-edit-text-field" + ) + @system_arguments[:label] ||= model.class.human_attribute_name(attribute) + end + + def call + form.text_field name: attribute, **@system_arguments + end + end + end + end +end diff --git a/app/components/open_project/common/inplace_edit_fields/text_input_component.sass b/app/components/open_project/common/inplace_edit_fields/text_input_component.sass new file mode 100644 index 00000000000..467636b6a47 --- /dev/null +++ b/app/components/open_project/common/inplace_edit_fields/text_input_component.sass @@ -0,0 +1,7 @@ +.op-inplace-edit-text-field + margin-left: -9px !important // cancel out 8px padding + 1px border + margin-right: -9px !important // cancel out 8px padding + 1px border + width: calc(100% + 18px) !important + &:not(&:focus):not(&:hover) + border-color: transparent + box-shadow: none diff --git a/config/initializers/inplace_edit_fields.rb b/config/initializers/inplace_edit_fields.rb new file mode 100644 index 00000000000..aef62f6e5e6 --- /dev/null +++ b/config/initializers/inplace_edit_fields.rb @@ -0,0 +1,34 @@ +# 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. +#++ + +Rails.application.config.to_prepare do + require_relative "../../lib/open_project/inplace_edit/registry" + OpenProject::InplaceFieldRegistry.register(:description, OpenProject::Common::InplaceEditFields::TextInputComponent) +end diff --git a/lib/open_project/inplace_edit/registry.rb b/lib/open_project/inplace_edit/registry.rb new file mode 100644 index 00000000000..a9f7e29c2f2 --- /dev/null +++ b/lib/open_project/inplace_edit/registry.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module OpenProject + class InplaceFieldRegistry + @registry = {} + + class << self + def register(attribute_name, field_component) + @registry[attribute_name.to_s] = field_component + end + + def fetch(attribute_name) + @registry.fetch(attribute_name.to_s) { Common::InplaceEditFields::TextInputComponent } + end + end + end +end diff --git a/modules/grids/app/components/grids/widgets/description.html.erb b/modules/grids/app/components/grids/widgets/description.html.erb index c9c75227b50..a5a7a18681f 100644 --- a/modules/grids/app/components/grids/widgets/description.html.erb +++ b/modules/grids/app/components/grids/widgets/description.html.erb @@ -31,7 +31,8 @@ See COPYRIGHT and LICENSE files for more details. <%= widget_wrapper do if project.description.present? - helpers.format_text project, :description + render OpenProject::Common::InplaceEditFieldComponent.new(model: @project, attribute: :description, visually_hide_label: true) + # helpers.format_text project, :description else render(Primer::Beta::Text.new(color: :subtle)) { t(:"js.grid.widgets.project_description.no_results") } end From 13664c2fe83661624efe6e7ae025a0de8632dff2 Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Wed, 21 Jan 2026 11:01:01 +0100 Subject: [PATCH 022/293] Create a update handler registry to be able to update different models from the inplaceEditField component --- .../inplace_edit_field_component.html.erb | 13 ++- .../common/inplace_edit_field_component.rb | 8 +- .../inplace_edit_fields_controller.rb | 98 +++++++++++++++++++ config/initializers/inplace_edit_fields.rb | 10 +- config/routes.rb | 5 + .../inplace_edit/field_registry.rb | 47 +++++++++ .../inplace_edit/handlers/default_update.rb | 41 ++++++++ .../project_update.rb} | 18 ++-- .../inplace_edit/update_registry.rb | 47 +++++++++ 9 files changed, 268 insertions(+), 19 deletions(-) create mode 100644 app/controllers/inplace_edit_fields_controller.rb create mode 100644 lib/open_project/inplace_edit/field_registry.rb create mode 100644 lib/open_project/inplace_edit/handlers/default_update.rb rename lib/open_project/inplace_edit/{registry.rb => handlers/project_update.rb} (80%) create mode 100644 lib/open_project/inplace_edit/update_registry.rb diff --git a/app/components/open_project/common/inplace_edit_field_component.html.erb b/app/components/open_project/common/inplace_edit_field_component.html.erb index 2a96ab623d9..a6109fefb3b 100644 --- a/app/components/open_project/common/inplace_edit_field_component.html.erb +++ b/app/components/open_project/common/inplace_edit_field_component.html.erb @@ -13,12 +13,19 @@ %> -
- <%= primer_form_with(model:, url: polymorphic_path(model), method: :patch, data: { turbo_stream: true }) do |form| +<%= component_wrapper(tag: :div) do %> + <%= primer_form_with( + model:, + url: inplace_edit_field_update_path(model: model.class.name, id: model.id, attribute:), + method: :patch, + data: { turbo_stream: true } + ) do |form| render_field_component = ->(f) { render field_component(f) } # The render_inline_form method looses context and thus does not know about the `field_component` method + system_arguments = @system_arguments render_inline_form(form) do |f| + f.hidden name: "system_arguments_json", value: system_arguments.to_json render_field_component.call(f) end end %> -
+<% end %> diff --git a/app/components/open_project/common/inplace_edit_field_component.rb b/app/components/open_project/common/inplace_edit_field_component.rb index a12590c57d7..6bb82fbf6d6 100644 --- a/app/components/open_project/common/inplace_edit_field_component.rb +++ b/app/components/open_project/common/inplace_edit_field_component.rb @@ -31,6 +31,8 @@ module OpenProject module Common class InplaceEditFieldComponent < ViewComponent::Base + include OpTurbo::Streamable + attr_reader :model, :attribute def initialize(model:, attribute:, **system_arguments) @@ -41,13 +43,9 @@ module OpenProject end def field_component(form) - klass = OpenProject::InplaceFieldRegistry.fetch(attribute) + klass = OpenProject::InplaceEdit::FieldRegistry.fetch(attribute) klass.new(form:, attribute:, model:, **@system_arguments) end - - def target_id - "#{model.model_name.singular}_#{model.id}_#{attribute}" - end end end end diff --git a/app/controllers/inplace_edit_fields_controller.rb b/app/controllers/inplace_edit_fields_controller.rb new file mode 100644 index 00000000000..6519e37897f --- /dev/null +++ b/app/controllers/inplace_edit_fields_controller.rb @@ -0,0 +1,98 @@ +# 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 InplaceEditFieldsController < ApplicationController + include OpTurbo::ComponentStream + + before_action :find_model + before_action :set_attribute + no_authorization_required! :update + + def update + handler = OpenProject::InplaceEdit::UpdateRegistry.fetch(@model) + + success = handler.call( + model: @model, + attribute: @attribute, + params: permitted_params, + user: current_user + ) + + if success + render_success_flash_message_via_turbo_stream( + message: I18n.t(:notice_successful_update) + ) + end + + replace_via_turbo_stream( + component:, + status: success ? :ok : :unprocessable_entity + ) + + respond_with_turbo_streams + end + + private + + def find_model + @model = + params[:model] + .constantize + .find(params[:id]) + rescue NameError, ActiveRecord::RecordNotFound + head :not_found + end + + def set_attribute + @attribute = params[:attribute].to_sym + end + + def permitted_params + params + .expect(@model.model_name.param_key => [@attribute]) + end + + def component + OpenProject::Common::InplaceEditFieldComponent.new( + model: @model, + attribute: @attribute, + **system_arguments.to_h.symbolize_keys + ) + end + + def system_arguments + arguments = params.to_unsafe_h + .values + .filter_map { |v| v["system_arguments_json"] } + .first + + JSON.parse(arguments) + end +end diff --git a/config/initializers/inplace_edit_fields.rb b/config/initializers/inplace_edit_fields.rb index aef62f6e5e6..cb5e9c70702 100644 --- a/config/initializers/inplace_edit_fields.rb +++ b/config/initializers/inplace_edit_fields.rb @@ -29,6 +29,12 @@ #++ Rails.application.config.to_prepare do - require_relative "../../lib/open_project/inplace_edit/registry" - OpenProject::InplaceFieldRegistry.register(:description, OpenProject::Common::InplaceEditFields::TextInputComponent) + # Register the edit fields per attribute + require_relative "../../lib/open_project/inplace_edit/field_registry" + OpenProject::InplaceEdit::FieldRegistry.register(:description, OpenProject::Common::InplaceEditFields::TextInputComponent) + + # Register the update handler per model + require_relative "../../lib/open_project/inplace_edit/handlers/default_update" + require_relative "../../lib/open_project/inplace_edit/handlers/project_update" + OpenProject::InplaceEdit::UpdateRegistry.register(Project, OpenProject::InplaceEdit::Handlers::ProjectUpdate) end diff --git a/config/routes.rb b/config/routes.rb index ea0f61009d7..f021ff3880d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1056,6 +1056,11 @@ Rails.application.routes.draw do delete "Groups/:id", to: "groups#destroy" end + scope "inplace_edit_fields/:model/:id/:attribute", as: "inplace_edit_field" do + post :update, controller: "inplace_edit_fields", action: :update + patch :update, controller: "inplace_edit_fields", action: :update + end + if OpenProject::Configuration.lookbook_enabled? mount Primer::ViewComponents::Engine, at: "/" mount Lookbook::Engine, at: "/lookbook" diff --git a/lib/open_project/inplace_edit/field_registry.rb b/lib/open_project/inplace_edit/field_registry.rb new file mode 100644 index 00000000000..afe42f2fb06 --- /dev/null +++ b/lib/open_project/inplace_edit/field_registry.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module OpenProject + module InplaceEdit + class FieldRegistry + @registry = {} + + class << self + def register(attribute_name, field_component) + @registry[attribute_name.to_s] = field_component + end + + def fetch(attribute_name) + @registry.fetch(attribute_name.to_s) { Common::InplaceEditFields::TextInputComponent } + end + end + end + end +end diff --git a/lib/open_project/inplace_edit/handlers/default_update.rb b/lib/open_project/inplace_edit/handlers/default_update.rb new file mode 100644 index 00000000000..b2db6aaf68b --- /dev/null +++ b/lib/open_project/inplace_edit/handlers/default_update.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module OpenProject + module InplaceEdit + module Handlers + class DefaultUpdate + def self.call(model:, attribute:, params:, user:) + model.update!(params.slice(attribute)) + end + end + end + end +end diff --git a/lib/open_project/inplace_edit/registry.rb b/lib/open_project/inplace_edit/handlers/project_update.rb similarity index 80% rename from lib/open_project/inplace_edit/registry.rb rename to lib/open_project/inplace_edit/handlers/project_update.rb index a9f7e29c2f2..865a5a38cee 100644 --- a/lib/open_project/inplace_edit/registry.rb +++ b/lib/open_project/inplace_edit/handlers/project_update.rb @@ -29,16 +29,16 @@ #++ module OpenProject - class InplaceFieldRegistry - @registry = {} + module InplaceEdit + module Handlers + class ProjectUpdate + def self.call(model:, attribute:, params:, user:) + call = ::Projects::UpdateService + .new(model:, user:) + .call(params) - class << self - def register(attribute_name, field_component) - @registry[attribute_name.to_s] = field_component - end - - def fetch(attribute_name) - @registry.fetch(attribute_name.to_s) { Common::InplaceEditFields::TextInputComponent } + call.success? + end end end end diff --git a/lib/open_project/inplace_edit/update_registry.rb b/lib/open_project/inplace_edit/update_registry.rb new file mode 100644 index 00000000000..893503e0d0a --- /dev/null +++ b/lib/open_project/inplace_edit/update_registry.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module OpenProject + module InplaceEdit + class UpdateRegistry + @registry = {} + + class << self + def register(model_class, handler) + @registry[model_class.name] = handler + end + + def fetch(model) + @registry.fetch(model.class.name, OpenProject::InplaceEdit::Handlers::DefaultUpdate) + end + end + end + end +end From 1b7e30eb49f0ca164ea3c6f7e59ab7dd7e09b903 Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Thu, 22 Jan 2026 14:49:31 +0100 Subject: [PATCH 023/293] Do a permission check in the InplaceEditField based on the contracts --- .../common/inplace_edit_field_component.rb | 18 +++++++++++++- .../inplace_edit_fields_controller.rb | 24 +++++++++++++------ config/initializers/inplace_edit_fields.rb | 5 +++- .../inplace_edit/handlers/default_update.rb | 5 ++-- .../inplace_edit/handlers/project_update.rb | 2 +- .../inplace_edit/update_registry.rb | 22 +++++++++++++---- 6 files changed, 60 insertions(+), 16 deletions(-) diff --git a/app/components/open_project/common/inplace_edit_field_component.rb b/app/components/open_project/common/inplace_edit_field_component.rb index 6bb82fbf6d6..c993f337c17 100644 --- a/app/components/open_project/common/inplace_edit_field_component.rb +++ b/app/components/open_project/common/inplace_edit_field_component.rb @@ -44,7 +44,23 @@ module OpenProject def field_component(form) klass = OpenProject::InplaceEdit::FieldRegistry.fetch(attribute) - klass.new(form:, attribute:, model:, **@system_arguments) + klass.new(form:, attribute:, model:, **merged_system_arguments) + end + + private + + def merged_system_arguments + @system_arguments.merge( + disabled: !writable? + ) + end + + def writable? + contract_class = OpenProject::InplaceEdit::UpdateRegistry.fetch_contract(model) + + return false unless contract_class + + contract_class.new(model, User.current).writable?(attribute) end end end diff --git a/app/controllers/inplace_edit_fields_controller.rb b/app/controllers/inplace_edit_fields_controller.rb index 6519e37897f..55656f46f72 100644 --- a/app/controllers/inplace_edit_fields_controller.rb +++ b/app/controllers/inplace_edit_fields_controller.rb @@ -36,11 +36,10 @@ class InplaceEditFieldsController < ApplicationController no_authorization_required! :update def update - handler = OpenProject::InplaceEdit::UpdateRegistry.fetch(@model) + handler = OpenProject::InplaceEdit::UpdateRegistry.fetch_handler(@model) success = handler.call( model: @model, - attribute: @attribute, params: permitted_params, user: current_user ) @@ -62,14 +61,25 @@ class InplaceEditFieldsController < ApplicationController private def find_model - @model = - params[:model] - .constantize - .find(params[:id]) - rescue NameError, ActiveRecord::RecordNotFound + model_class = resolve_model_class(params[:model]) + @model = model_class.visible + .find(params[:id]) + rescue NameError, ActiveRecord::RecordNotFound, ArgumentError, NoMethodError head :not_found end + def resolve_model_class(model_param) + return nil if model_param.blank? + + class_name = model_param.to_s.camelize + # Only allow models that are registered for inplace updates. + unless OpenProject::InplaceEdit::UpdateRegistry.registered?(class_name) + raise ArgumentError, "Unsupported model for inplace edit" + end + + class_name.constantize + end + def set_attribute @attribute = params[:attribute].to_sym end diff --git a/config/initializers/inplace_edit_fields.rb b/config/initializers/inplace_edit_fields.rb index cb5e9c70702..92d5a98dc0c 100644 --- a/config/initializers/inplace_edit_fields.rb +++ b/config/initializers/inplace_edit_fields.rb @@ -36,5 +36,8 @@ Rails.application.config.to_prepare do # Register the update handler per model require_relative "../../lib/open_project/inplace_edit/handlers/default_update" require_relative "../../lib/open_project/inplace_edit/handlers/project_update" - OpenProject::InplaceEdit::UpdateRegistry.register(Project, OpenProject::InplaceEdit::Handlers::ProjectUpdate) + require_relative "../../app/contracts/projects/update_contract" + OpenProject::InplaceEdit::UpdateRegistry.register(Project, + handler: OpenProject::InplaceEdit::Handlers::ProjectUpdate, + contract: Projects::UpdateContract) end diff --git a/lib/open_project/inplace_edit/handlers/default_update.rb b/lib/open_project/inplace_edit/handlers/default_update.rb index b2db6aaf68b..20d9e92c038 100644 --- a/lib/open_project/inplace_edit/handlers/default_update.rb +++ b/lib/open_project/inplace_edit/handlers/default_update.rb @@ -32,8 +32,9 @@ module OpenProject module InplaceEdit module Handlers class DefaultUpdate - def self.call(model:, attribute:, params:, user:) - model.update!(params.slice(attribute)) + def self.call(model:, params:, user:) + BaseServices::Update.new(model:, user:) + .call(params) end end end diff --git a/lib/open_project/inplace_edit/handlers/project_update.rb b/lib/open_project/inplace_edit/handlers/project_update.rb index 865a5a38cee..698cb2a8683 100644 --- a/lib/open_project/inplace_edit/handlers/project_update.rb +++ b/lib/open_project/inplace_edit/handlers/project_update.rb @@ -32,7 +32,7 @@ module OpenProject module InplaceEdit module Handlers class ProjectUpdate - def self.call(model:, attribute:, params:, user:) + def self.call(model:, params:, user:) call = ::Projects::UpdateService .new(model:, user:) .call(params) diff --git a/lib/open_project/inplace_edit/update_registry.rb b/lib/open_project/inplace_edit/update_registry.rb index 893503e0d0a..593d2202f8e 100644 --- a/lib/open_project/inplace_edit/update_registry.rb +++ b/lib/open_project/inplace_edit/update_registry.rb @@ -34,12 +34,26 @@ module OpenProject @registry = {} class << self - def register(model_class, handler) - @registry[model_class.name] = handler + def register(model_class, handler:, contract:) + @registry[model_class.name] = { + handler: handler, + contract: contract + } end - def fetch(model) - @registry.fetch(model.class.name, OpenProject::InplaceEdit::Handlers::DefaultUpdate) + def fetch_handler(model) + entry = @registry.fetch(model.class.name) + + entry ? entry[:handler] : OpenProject::InplaceEdit::Handlers::DefaultUpdate + end + + def fetch_contract(model) + entry = @registry.fetch(model.class.name) + entry && entry[:contract] + end + + def registered?(model_class) + @registry.key?(model_class) end end end From f9d251904c9af5f0e53a6c4e0ed3423a19c5facf Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Fri, 23 Jan 2026 14:46:12 +0100 Subject: [PATCH 024/293] Add RichTextAreaComponent to inplace edit fields --- .../rich_text_area_component.rb | 71 +++++++++++++++++++ .../text_input_component.rb | 3 +- .../text_input_component.sass | 2 +- .../inplace_edit_fields_controller.rb | 9 ++- config/initializers/inplace_edit_fields.rb | 6 +- config/routes.rb | 1 + .../grids/widgets/description.html.erb | 12 +++- 7 files changed, 93 insertions(+), 11 deletions(-) create mode 100644 app/components/open_project/common/inplace_edit_fields/rich_text_area_component.rb diff --git a/app/components/open_project/common/inplace_edit_fields/rich_text_area_component.rb b/app/components/open_project/common/inplace_edit_fields/rich_text_area_component.rb new file mode 100644 index 00000000000..5e9a88291db --- /dev/null +++ b/app/components/open_project/common/inplace_edit_fields/rich_text_area_component.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module OpenProject + module Common + module InplaceEditFields + class RichTextAreaComponent < ViewComponent::Base + attr_reader :form, :attribute, :model + + def initialize(form:, attribute:, model:, **system_arguments) + super() + @form = form + @attribute = attribute + @model = model + @system_arguments = system_arguments + @system_arguments[:classes] = class_names( + @system_arguments[:classes], + "op-inplace-edit-field--text-area" + ) + @system_arguments[:label] ||= model.class.human_attribute_name(attribute) + + @system_arguments[:rich_text_options] ||= {} + @system_arguments[:rich_text_options][:primerized] = true + end + + def call + form.rich_text_area(name: attribute, **@system_arguments) + + form.group(layout: :horizontal) do |button_group| + button_group.submit(name: :reset, + type: :submit, + label: I18n.t(:button_cancel), + scheme: :default, + formaction: inplace_edit_field_reset_path(model: model.class.name, id: model.id, attribute:), + formmethod: :get) + button_group.submit(name: :submit, + label: I18n.t(:button_save), + scheme: :primary) + end + end + end + end + end +end diff --git a/app/components/open_project/common/inplace_edit_fields/text_input_component.rb b/app/components/open_project/common/inplace_edit_fields/text_input_component.rb index e68feb901ec..430d543c81c 100644 --- a/app/components/open_project/common/inplace_edit_fields/text_input_component.rb +++ b/app/components/open_project/common/inplace_edit_fields/text_input_component.rb @@ -42,8 +42,9 @@ module OpenProject @system_arguments = system_arguments @system_arguments[:classes] = class_names( @system_arguments[:classes], - "op-inplace-edit-text-field" + "op-inplace-edit-field--text-input" ) + @system_arguments[:placeholder] ||= "–" @system_arguments[:label] ||= model.class.human_attribute_name(attribute) end diff --git a/app/components/open_project/common/inplace_edit_fields/text_input_component.sass b/app/components/open_project/common/inplace_edit_fields/text_input_component.sass index 467636b6a47..c91c2b68a85 100644 --- a/app/components/open_project/common/inplace_edit_fields/text_input_component.sass +++ b/app/components/open_project/common/inplace_edit_fields/text_input_component.sass @@ -1,4 +1,4 @@ -.op-inplace-edit-text-field +.op-inplace-edit-field--text-input margin-left: -9px !important // cancel out 8px padding + 1px border margin-right: -9px !important // cancel out 8px padding + 1px border width: calc(100% + 18px) !important diff --git a/app/controllers/inplace_edit_fields_controller.rb b/app/controllers/inplace_edit_fields_controller.rb index 55656f46f72..303135f0263 100644 --- a/app/controllers/inplace_edit_fields_controller.rb +++ b/app/controllers/inplace_edit_fields_controller.rb @@ -33,7 +33,7 @@ class InplaceEditFieldsController < ApplicationController before_action :find_model before_action :set_attribute - no_authorization_required! :update + no_authorization_required! :update, :reset def update handler = OpenProject::InplaceEdit::UpdateRegistry.fetch_handler(@model) @@ -58,6 +58,11 @@ class InplaceEditFieldsController < ApplicationController respond_with_turbo_streams end + def reset + replace_via_turbo_stream(component:) + respond_with_turbo_streams + end + private def find_model @@ -103,6 +108,6 @@ class InplaceEditFieldsController < ApplicationController .filter_map { |v| v["system_arguments_json"] } .first - JSON.parse(arguments) + arguments.nil? ? {} : JSON.parse(arguments) end end diff --git a/config/initializers/inplace_edit_fields.rb b/config/initializers/inplace_edit_fields.rb index 92d5a98dc0c..2b3674c6540 100644 --- a/config/initializers/inplace_edit_fields.rb +++ b/config/initializers/inplace_edit_fields.rb @@ -30,13 +30,9 @@ Rails.application.config.to_prepare do # Register the edit fields per attribute - require_relative "../../lib/open_project/inplace_edit/field_registry" - OpenProject::InplaceEdit::FieldRegistry.register(:description, OpenProject::Common::InplaceEditFields::TextInputComponent) + OpenProject::InplaceEdit::FieldRegistry.register(:description, OpenProject::Common::InplaceEditFields::RichTextAreaComponent) # Register the update handler per model - require_relative "../../lib/open_project/inplace_edit/handlers/default_update" - require_relative "../../lib/open_project/inplace_edit/handlers/project_update" - require_relative "../../app/contracts/projects/update_contract" OpenProject::InplaceEdit::UpdateRegistry.register(Project, handler: OpenProject::InplaceEdit::Handlers::ProjectUpdate, contract: Projects::UpdateContract) diff --git a/config/routes.rb b/config/routes.rb index f021ff3880d..1e7f98d8aad 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1059,6 +1059,7 @@ Rails.application.routes.draw do scope "inplace_edit_fields/:model/:id/:attribute", as: "inplace_edit_field" do post :update, controller: "inplace_edit_fields", action: :update patch :update, controller: "inplace_edit_fields", action: :update + get :reset, controller: "inplace_edit_fields", action: :reset end if OpenProject::Configuration.lookbook_enabled? diff --git a/modules/grids/app/components/grids/widgets/description.html.erb b/modules/grids/app/components/grids/widgets/description.html.erb index a5a7a18681f..b7bea2c176a 100644 --- a/modules/grids/app/components/grids/widgets/description.html.erb +++ b/modules/grids/app/components/grids/widgets/description.html.erb @@ -31,8 +31,16 @@ See COPYRIGHT and LICENSE files for more details. <%= widget_wrapper do if project.description.present? - render OpenProject::Common::InplaceEditFieldComponent.new(model: @project, attribute: :description, visually_hide_label: true) - # helpers.format_text project, :description + render OpenProject::Common::InplaceEditFieldComponent.new( + model: @project, + attribute: :description, + visually_hide_label: true, + rich_text_options: { + showAttachments: false, + editorType: "constrained", + macros: false + } + ) else render(Primer::Beta::Text.new(color: :subtle)) { t(:"js.grid.widgets.project_description.no_results") } end From c40349223d0b79127a09bf02438a25a7c4241a3e Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Mon, 26 Jan 2026 12:14:15 +0100 Subject: [PATCH 025/293] Add a real switch between Display and Edit Field for RichTextArea --- .../inplace_edit_field_component.html.erb | 47 ++++------- .../common/inplace_edit_field_component.rb | 42 +++++++--- .../display_fields/display_fields.sass | 16 ++++ .../rich_text_area_component.html.erb | 5 ++ .../rich_text_area_component.rb | 83 +++++++++++++++++++ .../common/inplace_edit_fields/index.sass | 1 + .../rich_text_area_component.rb | 5 ++ .../text_input_component.sass | 1 + .../inplace_edit_fields_controller.rb | 24 ++++-- config/routes.rb | 1 + .../dynamic/inplace-edit.controller.ts | 55 ++++++++++++ 11 files changed, 232 insertions(+), 48 deletions(-) create mode 100644 app/components/open_project/common/inplace_edit_fields/display_fields/display_fields.sass create mode 100644 app/components/open_project/common/inplace_edit_fields/display_fields/rich_text_area_component.html.erb create mode 100644 app/components/open_project/common/inplace_edit_fields/display_fields/rich_text_area_component.rb create mode 100644 frontend/src/stimulus/controllers/dynamic/inplace-edit.controller.ts diff --git a/app/components/open_project/common/inplace_edit_field_component.html.erb b/app/components/open_project/common/inplace_edit_field_component.html.erb index a6109fefb3b..6f3533c9b8e 100644 --- a/app/components/open_project/common/inplace_edit_field_component.html.erb +++ b/app/components/open_project/common/inplace_edit_field_component.html.erb @@ -1,31 +1,20 @@ -<%# system_arguments = @system_arguments %> +<%= component_wrapper(tag: :div, class: "op-inplace-edit") do %> + <% if display_field_component.present? && !enforce_edit_mode %> + <%= render display_field_component %> + <% else %> + <%= primer_form_with( + model:, + url: inplace_edit_field_update_path(model: model.class.name, id: model.id, attribute:), + method: :patch, + data: { turbo_stream: true } + ) do |form| + render_field_component = ->(f) { render edit_field_component(f) } # The render_inline_form method looses context and thus does not know about the `field_component` method + system_arguments = @system_arguments -<%#= - - - - - - - - - - - %> - -<%= component_wrapper(tag: :div) do %> - <%= primer_form_with( - model:, - url: inplace_edit_field_update_path(model: model.class.name, id: model.id, attribute:), - method: :patch, - data: { turbo_stream: true } - ) do |form| - render_field_component = ->(f) { render field_component(f) } # The render_inline_form method looses context and thus does not know about the `field_component` method - system_arguments = @system_arguments - - render_inline_form(form) do |f| - f.hidden name: "system_arguments_json", value: system_arguments.to_json - render_field_component.call(f) - end - end %> + render_inline_form(form) do |f| + f.hidden name: "system_arguments_json", value: system_arguments.to_json + render_field_component.call(f) + end + end %> + <% end %> <% end %> diff --git a/app/components/open_project/common/inplace_edit_field_component.rb b/app/components/open_project/common/inplace_edit_field_component.rb index c993f337c17..38e75cef5b8 100644 --- a/app/components/open_project/common/inplace_edit_field_component.rb +++ b/app/components/open_project/common/inplace_edit_field_component.rb @@ -33,28 +33,46 @@ module OpenProject class InplaceEditFieldComponent < ViewComponent::Base include OpTurbo::Streamable - attr_reader :model, :attribute + attr_reader :model, :attribute, :enforce_edit_mode - def initialize(model:, attribute:, **system_arguments) + def initialize(model:, attribute:, enforce_edit_mode: false, **system_arguments) super() @model = model @attribute = attribute + @enforce_edit_mode = enforce_edit_mode @system_arguments = system_arguments - end - - def field_component(form) - klass = OpenProject::InplaceEdit::FieldRegistry.fetch(attribute) - klass.new(form:, attribute:, model:, **merged_system_arguments) - end - - private - - def merged_system_arguments @system_arguments.merge( disabled: !writable? ) end + def field_class + OpenProject::InplaceEdit::FieldRegistry.fetch(attribute) + end + + def edit_field_component(form) + field_class.new( + form:, + attribute:, + model:, + **@system_arguments + ) + end + + def display_field_class + if field_class.respond_to?(:display_class) + field_class.display_class + else + nil + end + end + + def display_field_component + display_field_class.new(model:, attribute:, writable: writable?, **@system_arguments) + end + + private + def writable? contract_class = OpenProject::InplaceEdit::UpdateRegistry.fetch_contract(model) diff --git a/app/components/open_project/common/inplace_edit_fields/display_fields/display_fields.sass b/app/components/open_project/common/inplace_edit_fields/display_fields/display_fields.sass new file mode 100644 index 00000000000..e38a6a57830 --- /dev/null +++ b/app/components/open_project/common/inplace_edit_fields/display_fields/display_fields.sass @@ -0,0 +1,16 @@ +.op-inplace-edit + &--display-field + &_editable + margin-left: -9px !important // cancel out 8px padding + 1px border + margin-right: -9px !important // cancel out 8px padding + 1px border + padding: var(--base-size-8) + width: calc(100% + 18px) !important + border: 1px solid transparent + border-radius: var(--borderRadius-medium) + + &:hover, &:focus + border-color: var(--borderColor-default) + box-shadow: var(--shadow-inset) + + &:not(&_editable) + cursor: not-allowed diff --git a/app/components/open_project/common/inplace_edit_fields/display_fields/rich_text_area_component.html.erb b/app/components/open_project/common/inplace_edit_fields/display_fields/rich_text_area_component.html.erb new file mode 100644 index 00000000000..07d37591341 --- /dev/null +++ b/app/components/open_project/common/inplace_edit_fields/display_fields/rich_text_area_component.html.erb @@ -0,0 +1,5 @@ +<%= render(Primer::BaseComponent.new(tag: :div, **@display_field_arguments)) do %> +
+ <%= render_display_value %> +
+<% end %> diff --git a/app/components/open_project/common/inplace_edit_fields/display_fields/rich_text_area_component.rb b/app/components/open_project/common/inplace_edit_fields/display_fields/rich_text_area_component.rb new file mode 100644 index 00000000000..59db7619ec8 --- /dev/null +++ b/app/components/open_project/common/inplace_edit_fields/display_fields/rich_text_area_component.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module OpenProject + module Common + module InplaceEditFields + module DisplayFields + class RichTextAreaComponent < ViewComponent::Base + include OpenProject::TextFormatting + + attr_reader :model, :attribute, :writable + + def initialize(model:, attribute:, writable:, **system_arguments) + super() + @model = model + @attribute = attribute + @writable = writable + @system_arguments = system_arguments + end + + def render_display_value + value = model.public_send(attribute) + + if value.present? + format_text(value) + else + "–" + end + end + + def before_render + @display_field_arguments = { + classes: "op-inplace-edit--display-field #{'op-inplace-edit--display-field_editable' if writable}", + data: { + controller: "inplace-edit", + inplace_edit_url_value: edit_url, + action: writable ? "click->inplace-edit#activate" : "" + } + } + end + + private + + def edit_url + inplace_edit_field_edit_path( + model: model.class.name, + id: model.id, + attribute:, + system_arguments_json: @system_arguments.to_json + ) + end + end + end + end + end +end diff --git a/app/components/open_project/common/inplace_edit_fields/index.sass b/app/components/open_project/common/inplace_edit_fields/index.sass index 3543c70e587..117344481e4 100644 --- a/app/components/open_project/common/inplace_edit_fields/index.sass +++ b/app/components/open_project/common/inplace_edit_fields/index.sass @@ -1 +1,2 @@ @import "text_input_component" +@import "display_fields/display_fields" diff --git a/app/components/open_project/common/inplace_edit_fields/rich_text_area_component.rb b/app/components/open_project/common/inplace_edit_fields/rich_text_area_component.rb index 5e9a88291db..f2ccd5f8a4b 100644 --- a/app/components/open_project/common/inplace_edit_fields/rich_text_area_component.rb +++ b/app/components/open_project/common/inplace_edit_fields/rich_text_area_component.rb @@ -32,8 +32,13 @@ module OpenProject module Common module InplaceEditFields class RichTextAreaComponent < ViewComponent::Base + attr_reader :form, :attribute, :model + def self.display_class + DisplayFields::RichTextAreaComponent + end + def initialize(form:, attribute:, model:, **system_arguments) super() @form = form diff --git a/app/components/open_project/common/inplace_edit_fields/text_input_component.sass b/app/components/open_project/common/inplace_edit_fields/text_input_component.sass index c91c2b68a85..b520365388c 100644 --- a/app/components/open_project/common/inplace_edit_fields/text_input_component.sass +++ b/app/components/open_project/common/inplace_edit_fields/text_input_component.sass @@ -2,6 +2,7 @@ margin-left: -9px !important // cancel out 8px padding + 1px border margin-right: -9px !important // cancel out 8px padding + 1px border width: calc(100% + 18px) !important + &:not(&:focus):not(&:hover) border-color: transparent box-shadow: none diff --git a/app/controllers/inplace_edit_fields_controller.rb b/app/controllers/inplace_edit_fields_controller.rb index 303135f0263..7bd6edcab1d 100644 --- a/app/controllers/inplace_edit_fields_controller.rb +++ b/app/controllers/inplace_edit_fields_controller.rb @@ -33,7 +33,16 @@ class InplaceEditFieldsController < ApplicationController before_action :find_model before_action :set_attribute - no_authorization_required! :update, :reset + no_authorization_required! :edit, :update, :reset + + def edit + replace_via_turbo_stream( + component: component(enforce_edit_mode: true), + status: :ok + ) + + respond_with_turbo_streams + end def update handler = OpenProject::InplaceEdit::UpdateRegistry.fetch_handler(@model) @@ -51,7 +60,7 @@ class InplaceEditFieldsController < ApplicationController end replace_via_turbo_stream( - component:, + component: component(enforce_edit_mode: !success), status: success ? :ok : :unprocessable_entity ) @@ -94,19 +103,20 @@ class InplaceEditFieldsController < ApplicationController .expect(@model.model_name.param_key => [@attribute]) end - def component + def component(enforce_edit_mode: false) OpenProject::Common::InplaceEditFieldComponent.new( model: @model, attribute: @attribute, + enforce_edit_mode:, **system_arguments.to_h.symbolize_keys ) end def system_arguments - arguments = params.to_unsafe_h - .values - .filter_map { |v| v["system_arguments_json"] } - .first + arguments = params[:system_arguments_json].presence || params.to_unsafe_h + .values + .filter_map { |v| v["system_arguments_json"] } + .first arguments.nil? ? {} : JSON.parse(arguments) end diff --git a/config/routes.rb b/config/routes.rb index 1e7f98d8aad..fb28cbfc58b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1060,6 +1060,7 @@ Rails.application.routes.draw do post :update, controller: "inplace_edit_fields", action: :update patch :update, controller: "inplace_edit_fields", action: :update get :reset, controller: "inplace_edit_fields", action: :reset + get :edit, controller: "inplace_edit_fields", action: :edit end if OpenProject::Configuration.lookbook_enabled? diff --git a/frontend/src/stimulus/controllers/dynamic/inplace-edit.controller.ts b/frontend/src/stimulus/controllers/dynamic/inplace-edit.controller.ts new file mode 100644 index 00000000000..e306589242a --- /dev/null +++ b/frontend/src/stimulus/controllers/dynamic/inplace-edit.controller.ts @@ -0,0 +1,55 @@ +/* + * -- 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. + * ++ + * + */ + +import { Controller } from '@hotwired/stimulus'; +import { renderStreamMessage } from '@hotwired/turbo'; + +export default class extends Controller { + static values = { + url: String, + }; + + declare urlValue:string; + + async activate() { + const response = await fetch(this.urlValue, { + method: 'GET', + headers: { Accept: 'text/vnd.turbo-stream.html' }, + credentials: 'same-origin', + }); + + if (response.ok) { + renderStreamMessage(await response.text()); + } else { + throw new Error(response.statusText); + } + } +} From b2955ffad74f32bc175d396bc7ff9fcdef28df3a Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Mon, 26 Jan 2026 15:38:28 +0100 Subject: [PATCH 026/293] Add Lookbook docs for inplaceEditFields --- .../patterns/06-inplace-edit_fields.md.erb | 185 ++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 lookbook/docs/patterns/06-inplace-edit_fields.md.erb diff --git a/lookbook/docs/patterns/06-inplace-edit_fields.md.erb b/lookbook/docs/patterns/06-inplace-edit_fields.md.erb new file mode 100644 index 00000000000..1e2b24c1f1f --- /dev/null +++ b/lookbook/docs/patterns/06-inplace-edit_fields.md.erb @@ -0,0 +1,185 @@ +This document describes the architecture, usage, and extension points of the Inplace Edit system. +The goal is to provide a reusable, attribute-driven inline editing mechanism without touching existing update controllers. + +--- + +## High-Level Architecture + +The InplaceEdit system consists of: + +- **A generic wrapper component** + (`InplaceEditFieldComponent`) +- **Edit field components** + (e.g. `TextInputComponent`, `RichTextAreaComponent`) +- **Optional display field components** +- **A central registry** +- **A generic controller** +- **TurboStreams + Stimulus** for lazy loading + +Depending on the field type, two strategies are used: + +| Field type | Strategy | +|-------------------------------------------------|----------| +| Simple inputs (text, checkbox) | **Eager Edit (CSS switch)** | +| Complex inputs (RichTextAreas, autocompleteres) | **Lazy Edit (TurboStream)** | + +## Usage + +```ruby +OpenProject::Common::InplaceEditFieldComponent.new( + model: @project, + attribute: :description +) +``` + +### Central Components: + +#### InplaceEditFieldComponent +The `InplaceEditFieldComponent` is the **single entry point used in views**. +It is initialized with a model and an attribute and decides which concrete field component to render. It also decides whether the component is currently in display mode or edit mode. + +Only model and attribute are required. All additional keyword arguments are treated as system arguments and forwarded unchanged through Turbo roundtrips. Editability is determined via a contract and exposed through the `writable?` check. + +The component resolves the edit field via the `FieldRegistry` and optionally a display field via the edit field’s `display_class`. + +**Simplified HTML of the `InplaceEditFieldComponent`:** +```html +<%= component_wrapper(tag: :div, class: "op-inplace-edit") do + if display_field_component.present? && !enforce_edit_mode + render display_field_component + else + primer_form_with( + model:, + url: inplace_edit_field_update_path(model:, id:, attribute:), + ) do |form| + render_inline_form(form) do |f| + f.hidden name: "system_arguments_json", value: system_arguments.to_json + render edit_field_component(f) + end + end + end +end %> +``` + +**tl;dr**: This component is responsible for: + +- selecting the correct edit field +- deciding between lazy and eager edit +- if needed: rendering the appropriate display field +- checking whether the attribute is writable + +#### FieldRegistry + +The `FieldRegistry` maps attribute names to edit field components. The mapping is attribute-based and not model-specific. If no mapping exists for an attribute, a default text input component is used. + +Thus, the same attribute always renders the same component across different models. + +**Example registration:** +```ruby +OpenProject::InplaceEdit::FieldRegistry.register( + :description, + OpenProject::Common::InplaceEditFields::RichTextAreaComponent +) +``` + +#### EditFieldComponents + +`EditFieldComponents` are responsible for rendering the actual form field. They receive a form builder, the model, the attribute, and the forwarded system arguments. + +They may render only the field itself or also include submit and reset buttons. Richer fields such as CkEditors typically render their own action buttons, while simpler fields can rely on outer form handling. + +Edit field components may define a `display_class`. If present, this class is used to render the read-only display state. + +**Simplified example of an `EditFieldComponent`:** +```ruby +module OpenProject + module Common + module InplaceEditFields + class RichTextAreaComponent < ViewComponent::Base + def self.display_class + DisplayFields::RichTextAreaComponent + end + + def initialize(form:, attribute:, model:, **system_arguments) + super() + @form = form + @attribute = attribute + @model = model + @system_arguments = system_arguments + end + + def call + form.rich_text_area(name: attribute, **@system_arguments) + + form.group(layout: :horizontal) do |button_group| + button_group.submit(name: :reset, + type: :submit, + label: I18n.t(:button_cancel), + formaction: inplace_edit_field_reset_path(model:, id:, attribute:), + formmethod: :get) + button_group.submit(name: :submit, + label: I18n.t(:button_save), + scheme: :primary) + end + end + end + end + end +end +``` + +#### DisplayFieldComponents + +`DisplayFieldComponents` render the attribute value in read-only mode. They handle formatting and attach the Stimulus controller that triggers the switch to edit mode. + +They expose the edit URL via data attributes and typically make the rendered value clickable when the attribute is writable. + +Display fields are optional. Not every edit field has or needs a display field. If an edit field component does not define a `display_class`, the `InplaceEditFieldComponent` will always render the edit field. +From a technical perspective, these fields are always in edit mode. There is no Turbo-based replacement between display and edit state. Any display versus edit behavior is handled purely via CSS. +This is useful for simple fields (like standard text inputs) which are thus high performant in switching modes + +### Update behaviour + +#### InplaceEditFieldsController + +The `InplaceEditFieldsController` is a generic controller shared by all `InplaceEditComponent`s. It dynamically resolves the model but only allows models that are registered in the `UpdateRegistry`. + +* The edit action replaces the display component with the edit component via Turbo Stream. +* The update action delegates persistence to a registered handler and then replaces the component. +* The reset action switches back to display mode without saving. + +The controller itself contains no model-specific logic. + +#### UpdateRegistry + +The `UpdateRegistry` maps models to update handlers and contracts. The handler performs the update, while the contract is responsible for authorization and validation. + +**Example update handler:** +```ruby +module OpenProject + module InplaceEdit + module Handlers + class ProjectUpdate + def self.call(model:, params:, user:) + call = ::Projects::UpdateService + .new(model:, user:) + .call(params) + + call.success? + end + end + end + end +end +``` + +## Adding new fields + +To add a new editable attribute, create an `EditFieldComponent` and register it in the `FieldRegistry`. Optionally provide a display component. + +No changes to the core component or controller should be required. + +## Supporting new models +To support a new model, implement an update handler and a contract and register both in the `UpdateRegistry`. + +No changes to the core component or controller should be required. From e53cb4f4970b2a74da1282273bfc220e5f992dd0 Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Tue, 27 Jan 2026 08:32:59 +0100 Subject: [PATCH 027/293] Enable inplace edit for project status description --- .../common/inplace_edit_field_component.rb | 6 ++++++ config/initializers/inplace_edit_fields.rb | 1 + .../components/grids/widgets/project_status.html.erb | 11 ++++++++++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/app/components/open_project/common/inplace_edit_field_component.rb b/app/components/open_project/common/inplace_edit_field_component.rb index 38e75cef5b8..cb742780d23 100644 --- a/app/components/open_project/common/inplace_edit_field_component.rb +++ b/app/components/open_project/common/inplace_edit_field_component.rb @@ -68,9 +68,15 @@ module OpenProject end def display_field_component + return nil if display_field_class.nil? + display_field_class.new(model:, attribute:, writable: writable?, **@system_arguments) end + def wrapper_key + "op-inplace-edit-field-component--#{@model.name.parameterize(separator: '_')}-#{model.id}--#{attribute.name}" + end + private def writable? diff --git a/config/initializers/inplace_edit_fields.rb b/config/initializers/inplace_edit_fields.rb index 2b3674c6540..5845f6864c7 100644 --- a/config/initializers/inplace_edit_fields.rb +++ b/config/initializers/inplace_edit_fields.rb @@ -31,6 +31,7 @@ Rails.application.config.to_prepare do # Register the edit fields per attribute OpenProject::InplaceEdit::FieldRegistry.register(:description, OpenProject::Common::InplaceEditFields::RichTextAreaComponent) + OpenProject::InplaceEdit::FieldRegistry.register(:status_explanation, OpenProject::Common::InplaceEditFields::RichTextAreaComponent) # Register the update handler per model OpenProject::InplaceEdit::UpdateRegistry.register(Project, diff --git a/modules/grids/app/components/grids/widgets/project_status.html.erb b/modules/grids/app/components/grids/widgets/project_status.html.erb index d5d1d64a554..a7c9c3c5424 100644 --- a/modules/grids/app/components/grids/widgets/project_status.html.erb +++ b/modules/grids/app/components/grids/widgets/project_status.html.erb @@ -36,7 +36,16 @@ See COPYRIGHT and LICENSE files for more details. end flex.with_row do - format_text(project, :status_explanation) + render OpenProject::Common::InplaceEditFieldComponent.new( + model: project, + attribute: :status_explanation, + visually_hide_label: true, + rich_text_options: { + showAttachments: false, + editorType: "constrained", + macros: false + } + ) end if project.project_creation_wizard_enabled From 93751c5cc4ddc554bf4015e93e804c2d69967972 Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Tue, 27 Jan 2026 08:33:43 +0100 Subject: [PATCH 028/293] Replace unsafe constantize call --- app/controllers/inplace_edit_fields_controller.rb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/controllers/inplace_edit_fields_controller.rb b/app/controllers/inplace_edit_fields_controller.rb index 7bd6edcab1d..f3a47cab0d1 100644 --- a/app/controllers/inplace_edit_fields_controller.rb +++ b/app/controllers/inplace_edit_fields_controller.rb @@ -91,7 +91,17 @@ class InplaceEditFieldsController < ApplicationController raise ArgumentError, "Unsupported model for inplace edit" end - class_name.constantize + model_class = class_name.safe_constantize + + # Guard against resolving arbitrary non-ActiveRecord constants. + unless model_class.is_a?(Class) && + defined?(ApplicationRecord) && + model_class < ApplicationRecord && + model_class.respond_to?(:visible) + raise ArgumentError, "Model is not an ActiveRecord model" + end + + model_class end def set_attribute From c09af64fe19bb1aa19d1070adf99361d224298c5 Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Tue, 27 Jan 2026 10:28:10 +0100 Subject: [PATCH 029/293] Add first tests for new inplace edit fields --- .../common/inplace_edit_field_component.rb | 18 +-- .../rich_text_area_component.rb | 1 - .../inplace_edit_field_component_spec.rb | 110 +++++++++++++ .../rich_text_area_component_spec.rb | 75 +++++++++ .../rich_text_area_component_spec.rb | 57 +++++++ .../text_input_component_spec.rb | 54 +++++++ .../inplace_edit_fields_controller_spec.rb | 147 ++++++++++++++++++ .../inplace_edit/field_registry_spec.rb | 66 ++++++++ .../inplace_edit/update_registry_spec.rb | 60 +++++++ 9 files changed, 578 insertions(+), 10 deletions(-) create mode 100644 spec/components/open_project/common/inplace_edit_field_component_spec.rb create mode 100644 spec/components/open_project/common/inplace_edit_fields/display_fields/rich_text_area_component_spec.rb create mode 100644 spec/components/open_project/common/inplace_edit_fields/rich_text_area_component_spec.rb create mode 100644 spec/components/open_project/common/inplace_edit_fields/text_input_component_spec.rb create mode 100644 spec/controllers/inplace_edit_fields_controller_spec.rb create mode 100644 spec/lib/open_project/inplace_edit/field_registry_spec.rb create mode 100644 spec/lib/open_project/inplace_edit/update_registry_spec.rb diff --git a/app/components/open_project/common/inplace_edit_field_component.rb b/app/components/open_project/common/inplace_edit_field_component.rb index cb742780d23..19f66be85bb 100644 --- a/app/components/open_project/common/inplace_edit_field_component.rb +++ b/app/components/open_project/common/inplace_edit_field_component.rb @@ -41,9 +41,7 @@ module OpenProject @attribute = attribute @enforce_edit_mode = enforce_edit_mode @system_arguments = system_arguments - @system_arguments.merge( - disabled: !writable? - ) + @system_arguments[:disabled] ||= !writable? end def field_class @@ -62,8 +60,6 @@ module OpenProject def display_field_class if field_class.respond_to?(:display_class) field_class.display_class - else - nil end end @@ -80,11 +76,15 @@ module OpenProject private def writable? + return @writable if defined?(@writable) + contract_class = OpenProject::InplaceEdit::UpdateRegistry.fetch_contract(model) - - return false unless contract_class - - contract_class.new(model, User.current).writable?(attribute) + @writable = + if contract_class.present? + contract_class.new(model, User.current).writable?(attribute) + else + false + end end end end diff --git a/app/components/open_project/common/inplace_edit_fields/rich_text_area_component.rb b/app/components/open_project/common/inplace_edit_fields/rich_text_area_component.rb index f2ccd5f8a4b..d51878ba021 100644 --- a/app/components/open_project/common/inplace_edit_fields/rich_text_area_component.rb +++ b/app/components/open_project/common/inplace_edit_fields/rich_text_area_component.rb @@ -32,7 +32,6 @@ module OpenProject module Common module InplaceEditFields class RichTextAreaComponent < ViewComponent::Base - attr_reader :form, :attribute, :model def self.display_class diff --git a/spec/components/open_project/common/inplace_edit_field_component_spec.rb b/spec/components/open_project/common/inplace_edit_field_component_spec.rb new file mode 100644 index 00000000000..8d309bc3736 --- /dev/null +++ b/spec/components/open_project/common/inplace_edit_field_component_spec.rb @@ -0,0 +1,110 @@ +# 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 OpenProject::Common::InplaceEditFieldComponent, type: :component do + include ViewComponent::TestHelpers + + let(:project) { build_stubbed(:project, description: "## Hello") } + let(:user) { build_stubbed(:user) } + let(:contract) do + contract = instance_double(BaseContract) + + allow(contract).to receive(:writable?) do |attribute| + allowed_attributes.include?(attribute.to_s) + end + + allow(contract) + .to receive(:model) + .and_return(instance_double(Project)) + + contract + end + + let(:contract_class) do + instance_double(Class).tap do |klass| + allow(klass).to receive(:new) + .with(project, user) + .and_return(contract) + end + end + + before do + allow(User).to receive(:current).and_return(user) + allow(OpenProject::InplaceEdit::UpdateRegistry) + .to receive(:fetch_contract) + .and_return(contract_class) + end + + context "when attribute is writable" do + let(:allowed_attributes) { %w(description) } + + it "renders display field by default" do + render_inline(described_class.new(model: project, attribute: :description)) + + expect(rendered_content) + .to have_css(".op-inplace-edit--display-field") + end + + it "renders edit field when enforce_edit_mode is true" do + render_inline( + described_class.new( + model: project, + attribute: :description, + enforce_edit_mode: true + ) + ) + + expect(rendered_content) + .to have_css("form") + end + end + + context "when attribute is not writable" do + let(:allowed_attributes) { %w() } + + it "does not mark display field as editable" do + render_inline(described_class.new(model: project, attribute: :description)) + + expect(rendered_content) + .not_to include("click->inplace-edit#activate") + end + + it "passes disabled=true to the edit field" do + render_inline(described_class.new( + model: project, + attribute: :name + )) + + expect(rendered_content) + .to have_field("project[name]", type: :text, disabled: true) + end + end +end diff --git a/spec/components/open_project/common/inplace_edit_fields/display_fields/rich_text_area_component_spec.rb b/spec/components/open_project/common/inplace_edit_fields/display_fields/rich_text_area_component_spec.rb new file mode 100644 index 00000000000..1a1d04b5a57 --- /dev/null +++ b/spec/components/open_project/common/inplace_edit_fields/display_fields/rich_text_area_component_spec.rb @@ -0,0 +1,75 @@ +# 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 OpenProject::Common::InplaceEditFields::DisplayFields::RichTextAreaComponent, + type: :component do + include ViewComponent::TestHelpers + + let(:project) { build_stubbed(:project, description: "## Hello") } + + it "renders formatted text" do + render_inline( + described_class.new( + model: project, + attribute: :description, + writable: true + ) + ) + + expect(rendered_content).to have_css("h2", text: "Hello") + end + + it "adds inplace-edit stimulus data when writable" do + render_inline( + described_class.new( + model: project, + attribute: :description, + writable: true + ) + ) + + expect(rendered_content) + .to include("data-action=\"click->inplace-edit#activate\"") + end + + it "adds no inplace-edit stimulus data when not writable" do + render_inline( + described_class.new( + model: project, + attribute: :description, + writable: false + ) + ) + + expect(rendered_content) + .not_to include("data-action=\"click->inplace-edit#activate\"") + end +end diff --git a/spec/components/open_project/common/inplace_edit_fields/rich_text_area_component_spec.rb b/spec/components/open_project/common/inplace_edit_fields/rich_text_area_component_spec.rb new file mode 100644 index 00000000000..8721f6e0530 --- /dev/null +++ b/spec/components/open_project/common/inplace_edit_fields/rich_text_area_component_spec.rb @@ -0,0 +1,57 @@ +# 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 OpenProject::Common::InplaceEditFields::RichTextAreaComponent, + type: :component do + include ViewComponent::TestHelpers + + let(:project) { build_stubbed(:project) } + + it "renders a rich text area and submit buttons" do + component_class = described_class + render_in_view_context(project) do |model| + primer_form_with(url: "/foo", model:) do |f| + render_inline_form(f) do |form| + render component_class.new( + form:, + model:, + attribute: :name + ) + end + end + end + + expect(rendered_content).to have_css("textarea[name='project[name]']", visible: :hidden) + expect(rendered_content).to have_css("opce-ckeditor-augmented-textarea") + expect(rendered_content).to have_button(I18n.t(:button_save)) + expect(rendered_content).to have_button(I18n.t(:button_cancel)) + end +end diff --git a/spec/components/open_project/common/inplace_edit_fields/text_input_component_spec.rb b/spec/components/open_project/common/inplace_edit_fields/text_input_component_spec.rb new file mode 100644 index 00000000000..9eec1d19f83 --- /dev/null +++ b/spec/components/open_project/common/inplace_edit_fields/text_input_component_spec.rb @@ -0,0 +1,54 @@ +# 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 OpenProject::Common::InplaceEditFields::TextInputComponent, + type: :component do + include ViewComponent::TestHelpers + + let(:project) { build_stubbed(:project) } + + it "renders a text input for the attribute" do + component_class = described_class + render_in_view_context(project) do |model| + primer_form_with(url: "/foo", model:) do |f| + render_inline_form(f) do |form| + render component_class.new( + form:, + model:, + attribute: :name + ) + end + end + end + + expect(rendered_content).to have_field("project[name]", type: "text") + end +end diff --git a/spec/controllers/inplace_edit_fields_controller_spec.rb b/spec/controllers/inplace_edit_fields_controller_spec.rb new file mode 100644 index 00000000000..9035f81993e --- /dev/null +++ b/spec/controllers/inplace_edit_fields_controller_spec.rb @@ -0,0 +1,147 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +require "rails_helper" + +RSpec.describe InplaceEditFieldsController do + let(:user) { create(:user) } + let(:model) { create(:project) } + let(:attribute) { :name } + let(:model_param) { "project" } + + before do + allow(controller).to receive(:current_user).and_return(user) + + allow(OpenProject::InplaceEdit::UpdateRegistry) + .to receive_messages(registered?: true, fetch_handler: handler) + + allow(Project) + .to receive(:visible) + .and_return(Project.all) + end + + describe "GET #edit" do + let(:handler) { double } + + it "returns a turbo stream response" do + get :edit, params: { + model: model_param, + id: model.id, + attribute: + }, format: :turbo_stream + + expect(response).to have_http_status(:ok) + expect(response.media_type).to eq("text/vnd.turbo-stream.html") + end + end + + describe "PATCH #update" do + let(:handler) { double(call: success) } + + context "when update is successful" do + let(:success) { true } + + it "returns ok and renders success flash" do + patch :update, params: { + model: model_param, + id: model.id, + attribute:, + project: { + name: "New project" + } + }, format: :turbo_stream + + expect(response).to have_http_status(:ok) + expect(response.media_type).to eq("text/vnd.turbo-stream.html") + end + end + + context "when update fails" do + let(:success) { false } + + it "returns unprocessable_entity and stays in edit mode" do + patch :update, params: { + model: model_param, + id: model.id, + attribute:, + project: { + name: "" + } + }, format: :turbo_stream + + expect(response).to have_http_status(:unprocessable_entity) + expect(response.media_type).to eq("text/vnd.turbo-stream.html") + end + end + end + + describe "POST #reset" do + let(:handler) { double } + + it "renders the component in view mode" do + post :reset, params: { + model: model_param, + id: model.id, + attribute: + }, format: :turbo_stream + + expect(response).to have_http_status(:ok) + expect(response.media_type).to eq("text/vnd.turbo-stream.html") + end + end + + describe "model resolution errors" do + let(:handler) { double } + + it "returns 404 for unsupported model" do + allow(OpenProject::InplaceEdit::UpdateRegistry) + .to receive(:registered?) + .and_return(false) + + get :edit, params: { + model: "invalid_model", + id: 123, + attribute: + }, format: :turbo_stream + + expect(response).to have_http_status(:not_found) + end + + it "returns 404 for missing record" do + get :edit, params: { + model: model_param, + id: -1, + attribute: + }, format: :turbo_stream + + expect(response).to have_http_status(:not_found) + end + end +end diff --git a/spec/lib/open_project/inplace_edit/field_registry_spec.rb b/spec/lib/open_project/inplace_edit/field_registry_spec.rb new file mode 100644 index 00000000000..3bea5340c31 --- /dev/null +++ b/spec/lib/open_project/inplace_edit/field_registry_spec.rb @@ -0,0 +1,66 @@ +# 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 OpenProject::InplaceEdit::FieldRegistry do + subject(:registry) { described_class } + + before do + registry.instance_variable_set(:@registry, {}) + end + + describe ".register" do + it "registers a field component for an attribute" do + registry.register(:description, :rich_text_component) + + expect(registry.fetch(:description)).to eq(:rich_text_component) + end + end + + describe ".fetch" do + it "returns the registered component for the attribute" do + registry.register(:description, :rich_text_component) + + expect(registry.fetch(:description)).to eq(:rich_text_component) + end + + it "falls back to TextInputComponent if attribute is not registered" do + expect(registry.fetch(:unknown)) + .to eq(OpenProject::Common::InplaceEditFields::TextInputComponent) + end + + it "normalizes attribute names to strings" do + registry.register("description", :rich_text_component) + + expect(registry.fetch(:description)).to eq(:rich_text_component) + end + end +end diff --git a/spec/lib/open_project/inplace_edit/update_registry_spec.rb b/spec/lib/open_project/inplace_edit/update_registry_spec.rb new file mode 100644 index 00000000000..9d544d4d923 --- /dev/null +++ b/spec/lib/open_project/inplace_edit/update_registry_spec.rb @@ -0,0 +1,60 @@ +# 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 OpenProject::InplaceEdit::UpdateRegistry do + let(:handler) { instance_double(OpenProject::InplaceEdit::Handlers::DefaultUpdate) } + let(:contract) { instance_double(Projects::UpdateContract) } + + before do + described_class.instance_variable_set(:@registry, {}) + end + + describe ".register" do + it "registers handler and contract for a model" do + described_class.register(Project, handler:, contract:) + + expect(described_class.fetch_handler(Project.new)).to eq(handler) + expect(described_class.fetch_contract(Project.new)).to eq(contract) + end + end + + describe ".registered?" do + it "returns true for registered model" do + described_class.register(Project, handler:, contract:) + + expect(described_class.registered?("Project")).to be(true) + end + + it "returns false for unregistered model" do + expect(described_class.registered?("Foo")).to be(false) + end + end +end From d0b4236da291c954cf938ef5cf76d12c4002fc12 Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Wed, 28 Jan 2026 09:00:09 +0100 Subject: [PATCH 030/293] Add feature test for updating project description inline --- .../inplace_edit_field_component.html.erb | 2 +- .../common/inplace_edit_field_component.rb | 4 + .../rich_text_area_component.rb | 6 +- .../grids/widgets/description.html.erb | 2 +- .../project_description_widget_spec.rb | 89 ++++++++++++++----- .../edit_fields/turbo/text_editor_field.rb | 17 ++++ 6 files changed, 94 insertions(+), 26 deletions(-) create mode 100644 spec/support/edit_fields/turbo/text_editor_field.rb diff --git a/app/components/open_project/common/inplace_edit_field_component.html.erb b/app/components/open_project/common/inplace_edit_field_component.html.erb index 6f3533c9b8e..01251f341c5 100644 --- a/app/components/open_project/common/inplace_edit_field_component.html.erb +++ b/app/components/open_project/common/inplace_edit_field_component.html.erb @@ -1,4 +1,4 @@ -<%= component_wrapper(tag: :div, class: "op-inplace-edit") do %> +<%= component_wrapper(tag: :div, class: "op-inplace-edit", data: { test_selector: wrapper_test_selector }) do %> <% if display_field_component.present? && !enforce_edit_mode %> <%= render display_field_component %> <% else %> diff --git a/app/components/open_project/common/inplace_edit_field_component.rb b/app/components/open_project/common/inplace_edit_field_component.rb index 19f66be85bb..240881a84f1 100644 --- a/app/components/open_project/common/inplace_edit_field_component.rb +++ b/app/components/open_project/common/inplace_edit_field_component.rb @@ -73,6 +73,10 @@ module OpenProject "op-inplace-edit-field-component--#{@model.name.parameterize(separator: '_')}-#{model.id}--#{attribute.name}" end + def wrapper_test_selector + "op-inplace-edit-field" + end + private def writable? diff --git a/app/components/open_project/common/inplace_edit_fields/rich_text_area_component.rb b/app/components/open_project/common/inplace_edit_fields/rich_text_area_component.rb index d51878ba021..732a0f3af0c 100644 --- a/app/components/open_project/common/inplace_edit_fields/rich_text_area_component.rb +++ b/app/components/open_project/common/inplace_edit_fields/rich_text_area_component.rb @@ -63,10 +63,12 @@ module OpenProject label: I18n.t(:button_cancel), scheme: :default, formaction: inplace_edit_field_reset_path(model: model.class.name, id: model.id, attribute:), - formmethod: :get) + formmethod: :get, + test_selector: "op-inplace-edit-field--textarea-cancel") button_group.submit(name: :submit, label: I18n.t(:button_save), - scheme: :primary) + scheme: :primary, + test_selector: "op-inplace-edit-field--textarea-save") end end end diff --git a/modules/grids/app/components/grids/widgets/description.html.erb b/modules/grids/app/components/grids/widgets/description.html.erb index b7bea2c176a..a05c868c5fc 100644 --- a/modules/grids/app/components/grids/widgets/description.html.erb +++ b/modules/grids/app/components/grids/widgets/description.html.erb @@ -29,7 +29,7 @@ See COPYRIGHT and LICENSE files for more details. ++#%> <%= - widget_wrapper do + widget_wrapper(test_selector: "op-overview-widget--project-description") do if project.description.present? render OpenProject::Common::InplaceEditFieldComponent.new( model: @project, diff --git a/modules/overviews/spec/features/project_description_widget_spec.rb b/modules/overviews/spec/features/project_description_widget_spec.rb index 3f0ef771da4..fa446185083 100644 --- a/modules/overviews/spec/features/project_description_widget_spec.rb +++ b/modules/overviews/spec/features/project_description_widget_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH @@ -31,8 +33,10 @@ require "spec_helper" require_relative "../support/pages/dashboard" RSpec.describe "Project description widget", :js, with_flag: { new_project_overview: true } do + include TestSelectorFinders + let!(:type) { create(:type) } - let!(:portfolio) { create(:portfolio, description: "") } + let!(:portfolio) { create(:portfolio, description: "A new description") } let!(:open_status) { create(:default_status) } let(:permissions) do @@ -54,25 +58,66 @@ RSpec.describe "Project description widget", :js, with_flag: { new_project_overv Pages::Dashboard.new(portfolio) end - context "as a user with permission" do - before do - login_as user + let(:overview_page) do + Pages::Projects::Show.new(portfolio) + end - dashboard_page.visit! + context "as a user with permission" do + context "on the dashboard" do + before do + login_as user + + dashboard_page.visit! + end + + it "adds a project description widget, and edits it correctly" do + expect(page).to have_current_path(dashboard_project_overview_path(portfolio)) + + # Find the project description widget area + description_widget_area = Components::Grids::GridArea.new("[data-test-selector*='grid-widget-project_description']") + description_widget_area.expect_to_exist + + # Edit the project description within the widget + within description_widget_area.area do + # Find the editable description field + description_field = TextEditorField.new(page, "description", + selector: "op-editable-attribute-field[fieldname='description']") + + # Activate the field for editing + description_field.activate! + + # Set a new description + new_description = "This is a **test** project description with markdown formatting." + description_field.set_value(new_description) + + # Save the changes + description_field.save! + end + + dashboard_page.expect_and_dismiss_toaster message: I18n.t("js.notice_successful_update") + + dashboard_page.visit! + expect(page).to have_content("This is a test project description with markdown formatting.") + + portfolio.reload + expect(portfolio.description).to include("This is a **test** project description") + end end - it "opens the dashboard, adds a project description widget, and edits it correctly" do - expect(page).to have_current_path(dashboard_project_overview_path(portfolio)) + context "on the overview" do + before do + login_as user - # Find the project description widget area - description_widget_area = Components::Grids::GridArea.new("[data-test-selector*='grid-widget-project_description']") - description_widget_area.expect_to_exist + overview_page.visit! + end + + it "opens the overview, and edits a project description correctly" do + expect(page).to have_current_path(project_overview_path(portfolio)) - # Edit the project description within the widget - within description_widget_area.area do # Find the editable description field - description_field = TextEditorField.new(page, "description", - selector: "op-editable-attribute-field[fieldname='description']") + description_field = Turbo::TextEditorField.new(page, + "description", + selector: test_selector("op-overview-widget--project-description")) # Activate the field for editing description_field.activate! @@ -83,15 +128,15 @@ RSpec.describe "Project description widget", :js, with_flag: { new_project_overv # Save the changes description_field.save! + + overview_page.expect_and_dismiss_flash message: I18n.t("js.notice_successful_update") + + overview_page.visit! + expect(page).to have_content("This is a test project description with markdown formatting.") + + portfolio.reload + expect(portfolio.description).to include("This is a **test** project description") end - - dashboard_page.expect_and_dismiss_toaster message: I18n.t("js.notice_successful_update") - - dashboard_page.visit! - expect(page).to have_content("This is a test project description with markdown formatting.") - - portfolio.reload - expect(portfolio.description).to include("This is a **test** project description") end end end diff --git a/spec/support/edit_fields/turbo/text_editor_field.rb b/spec/support/edit_fields/turbo/text_editor_field.rb new file mode 100644 index 00000000000..66f8213bf77 --- /dev/null +++ b/spec/support/edit_fields/turbo/text_editor_field.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative "../text_editor_field" + +module Turbo + class TextEditorField < ::TextEditorField + def display_selector + page.test_selector("op-inplace-edit-field") + end + + def control_link(action = :save) + raise "Invalid link" unless %i[save cancel].include?(action) + + "#{page.test_selector("op-inplace-edit-field--textarea-#{action}")}:not([disabled])" + end + end +end From 71592a79fcc02f982ce638fa55ac6e8cb65aefe0 Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Wed, 28 Jan 2026 12:19:54 +0100 Subject: [PATCH 031/293] Remove eager loading option from the inplace edit fields. Now all fields are rendered the same way, making it easier to understand und use the code. --- .../common/inplace_edit_field_component.rb | 3 +- .../display_fields/display_field_component.rb | 89 +++++++++++++++++++ ...lds.sass => display_fields_component.sass} | 0 .../rich_text_area_component.html.erb | 5 -- .../rich_text_area_component.rb | 47 ++-------- .../common/inplace_edit_fields/index.sass | 3 +- .../rich_text_area_component.rb | 2 +- .../text_input_component.rb | 26 ++++-- .../text_input_component.sass | 8 -- .../inplace_edit_fields_controller.rb | 36 ++++---- .../dynamic/inplace-edit.controller.ts | 2 +- .../inplace_edit/handlers/default_update.rb | 42 --------- .../inplace_edit/update_registry.rb | 19 ++-- ...s.md.erb => 06-inplace-edit-fields.md.erb} | 36 +++++--- .../inplace_edit_field_component_spec.rb | 14 +-- .../rich_text_area_component_spec.rb | 4 +- .../inplace_edit_fields_controller_spec.rb | 15 ++++ .../inplace_edit/update_registry_spec.rb | 10 ++- 18 files changed, 201 insertions(+), 160 deletions(-) create mode 100644 app/components/open_project/common/inplace_edit_fields/display_fields/display_field_component.rb rename app/components/open_project/common/inplace_edit_fields/display_fields/{display_fields.sass => display_fields_component.sass} (100%) delete mode 100644 app/components/open_project/common/inplace_edit_fields/display_fields/rich_text_area_component.html.erb delete mode 100644 app/components/open_project/common/inplace_edit_fields/text_input_component.sass delete mode 100644 lib/open_project/inplace_edit/handlers/default_update.rb rename lookbook/docs/patterns/{06-inplace-edit_fields.md.erb => 06-inplace-edit-fields.md.erb} (85%) diff --git a/app/components/open_project/common/inplace_edit_field_component.rb b/app/components/open_project/common/inplace_edit_field_component.rb index 240881a84f1..9e36edbd1b1 100644 --- a/app/components/open_project/common/inplace_edit_field_component.rb +++ b/app/components/open_project/common/inplace_edit_field_component.rb @@ -41,7 +41,6 @@ module OpenProject @attribute = attribute @enforce_edit_mode = enforce_edit_mode @system_arguments = system_arguments - @system_arguments[:disabled] ||= !writable? end def field_class @@ -60,6 +59,8 @@ module OpenProject def display_field_class if field_class.respond_to?(:display_class) field_class.display_class + else + InplaceEditFields::DisplayFields::DisplayFieldComponent end end diff --git a/app/components/open_project/common/inplace_edit_fields/display_fields/display_field_component.rb b/app/components/open_project/common/inplace_edit_fields/display_fields/display_field_component.rb new file mode 100644 index 00000000000..7be8c96cfac --- /dev/null +++ b/app/components/open_project/common/inplace_edit_fields/display_fields/display_field_component.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module OpenProject + module Common + module InplaceEditFields + module DisplayFields + class DisplayFieldComponent < ViewComponent::Base + include OpenProject::TextFormatting + + attr_reader :model, :attribute, :writable + + def initialize(model:, attribute:, writable:, **system_arguments) + super() + @model = model + @attribute = attribute + @writable = writable + @system_arguments = system_arguments + end + + def render_display_value + value = model.public_send(attribute) + + if value.present? + format_text(value) + else + "–" + end + end + + def display_field_arguments + @display_field_arguments ||= { + classes: "op-inplace-edit--display-field #{'op-inplace-edit--display-field_editable' if writable}", + data: { + controller: "inplace-edit", + inplace_edit_url_value: edit_url, + action: writable ? "click->inplace-edit#request" : "" + } + } + end + + def call + render(Primer::BaseComponent.new(tag: :div, **display_field_arguments)) do + render_display_value + end + end + + private + + def edit_url + inplace_edit_field_edit_path( + model: model.class.name, + id: model.id, + attribute:, + system_arguments_json: @system_arguments.to_json + ) + end + end + end + end + end +end diff --git a/app/components/open_project/common/inplace_edit_fields/display_fields/display_fields.sass b/app/components/open_project/common/inplace_edit_fields/display_fields/display_fields_component.sass similarity index 100% rename from app/components/open_project/common/inplace_edit_fields/display_fields/display_fields.sass rename to app/components/open_project/common/inplace_edit_fields/display_fields/display_fields_component.sass diff --git a/app/components/open_project/common/inplace_edit_fields/display_fields/rich_text_area_component.html.erb b/app/components/open_project/common/inplace_edit_fields/display_fields/rich_text_area_component.html.erb deleted file mode 100644 index 07d37591341..00000000000 --- a/app/components/open_project/common/inplace_edit_fields/display_fields/rich_text_area_component.html.erb +++ /dev/null @@ -1,5 +0,0 @@ -<%= render(Primer::BaseComponent.new(tag: :div, **@display_field_arguments)) do %> -
- <%= render_display_value %> -
-<% end %> diff --git a/app/components/open_project/common/inplace_edit_fields/display_fields/rich_text_area_component.rb b/app/components/open_project/common/inplace_edit_fields/display_fields/rich_text_area_component.rb index 59db7619ec8..f582d282d9c 100644 --- a/app/components/open_project/common/inplace_edit_fields/display_fields/rich_text_area_component.rb +++ b/app/components/open_project/common/inplace_edit_fields/display_fields/rich_text_area_component.rb @@ -32,50 +32,17 @@ module OpenProject module Common module InplaceEditFields module DisplayFields - class RichTextAreaComponent < ViewComponent::Base - include OpenProject::TextFormatting - + class RichTextAreaComponent < DisplayFieldComponent attr_reader :model, :attribute, :writable - def initialize(model:, attribute:, writable:, **system_arguments) - super() - @model = model - @attribute = attribute - @writable = writable - @system_arguments = system_arguments - end - - def render_display_value - value = model.public_send(attribute) - - if value.present? - format_text(value) - else - "–" + def call + render(Primer::BaseComponent.new(tag: :div, **display_field_arguments)) do + render(Primer::BaseComponent.new(tag: :div, + classes: "op-uc-container op-uc-container_reduced-headings -multiline")) do + render_display_value + end end end - - def before_render - @display_field_arguments = { - classes: "op-inplace-edit--display-field #{'op-inplace-edit--display-field_editable' if writable}", - data: { - controller: "inplace-edit", - inplace_edit_url_value: edit_url, - action: writable ? "click->inplace-edit#activate" : "" - } - } - end - - private - - def edit_url - inplace_edit_field_edit_path( - model: model.class.name, - id: model.id, - attribute:, - system_arguments_json: @system_arguments.to_json - ) - end end end end diff --git a/app/components/open_project/common/inplace_edit_fields/index.sass b/app/components/open_project/common/inplace_edit_fields/index.sass index 117344481e4..a45f5bcaf86 100644 --- a/app/components/open_project/common/inplace_edit_fields/index.sass +++ b/app/components/open_project/common/inplace_edit_fields/index.sass @@ -1,2 +1 @@ -@import "text_input_component" -@import "display_fields/display_fields" +@import "display_fields/display_fields_component" diff --git a/app/components/open_project/common/inplace_edit_fields/rich_text_area_component.rb b/app/components/open_project/common/inplace_edit_fields/rich_text_area_component.rb index 732a0f3af0c..a3bb16fbf58 100644 --- a/app/components/open_project/common/inplace_edit_fields/rich_text_area_component.rb +++ b/app/components/open_project/common/inplace_edit_fields/rich_text_area_component.rb @@ -57,7 +57,7 @@ module OpenProject def call form.rich_text_area(name: attribute, **@system_arguments) - form.group(layout: :horizontal) do |button_group| + form.group(layout: :horizontal, justify_content: :flex_end) do |button_group| button_group.submit(name: :reset, type: :submit, label: I18n.t(:button_cancel), diff --git a/app/components/open_project/common/inplace_edit_fields/text_input_component.rb b/app/components/open_project/common/inplace_edit_fields/text_input_component.rb index 430d543c81c..ae71deeb9b0 100644 --- a/app/components/open_project/common/inplace_edit_fields/text_input_component.rb +++ b/app/components/open_project/common/inplace_edit_fields/text_input_component.rb @@ -34,22 +34,36 @@ module OpenProject class TextInputComponent < ViewComponent::Base attr_reader :form, :attribute, :model + def self.display_class + DisplayFields::DisplayFieldComponent + end + def initialize(form:, attribute:, model:, **system_arguments) super() @form = form @attribute = attribute @model = model @system_arguments = system_arguments - @system_arguments[:classes] = class_names( - @system_arguments[:classes], - "op-inplace-edit-field--text-input" - ) - @system_arguments[:placeholder] ||= "–" @system_arguments[:label] ||= model.class.human_attribute_name(attribute) end def call - form.text_field name: attribute, **@system_arguments + form.text_field name: attribute, + data: { controller: "inplace-edit", + inplace_edit_url_value: reset_url, + action: "keydown.esc->inplace-edit#request" }, + **@system_arguments + end + + private + + def reset_url + inplace_edit_field_reset_path( + model: model.class.name, + id: model.id, + attribute:, + system_arguments_json: @system_arguments.to_json + ) end end end diff --git a/app/components/open_project/common/inplace_edit_fields/text_input_component.sass b/app/components/open_project/common/inplace_edit_fields/text_input_component.sass deleted file mode 100644 index b520365388c..00000000000 --- a/app/components/open_project/common/inplace_edit_fields/text_input_component.sass +++ /dev/null @@ -1,8 +0,0 @@ -.op-inplace-edit-field--text-input - margin-left: -9px !important // cancel out 8px padding + 1px border - margin-right: -9px !important // cancel out 8px padding + 1px border - width: calc(100% + 18px) !important - - &:not(&:focus):not(&:hover) - border-color: transparent - box-shadow: none diff --git a/app/controllers/inplace_edit_fields_controller.rb b/app/controllers/inplace_edit_fields_controller.rb index f3a47cab0d1..2b4bb22baae 100644 --- a/app/controllers/inplace_edit_fields_controller.rb +++ b/app/controllers/inplace_edit_fields_controller.rb @@ -47,11 +47,15 @@ class InplaceEditFieldsController < ApplicationController def update handler = OpenProject::InplaceEdit::UpdateRegistry.fetch_handler(@model) - success = handler.call( - model: @model, - params: permitted_params, - user: current_user - ) + if handler.present? + success = handler.call( + model: @model, + params: permitted_params, + user: current_user + ) + else + raise ArgumentError, "Missing update handler for #{@model}" + end if success render_success_flash_message_via_turbo_stream( @@ -65,6 +69,8 @@ class InplaceEditFieldsController < ApplicationController ) respond_with_turbo_streams + rescue ArgumentError + head :not_found end def reset @@ -76,29 +82,21 @@ class InplaceEditFieldsController < ApplicationController def find_model model_class = resolve_model_class(params[:model]) - @model = model_class.visible - .find(params[:id]) - rescue NameError, ActiveRecord::RecordNotFound, ArgumentError, NoMethodError + @model = model_class.visible.find(params[:id]) + rescue ActiveRecord::RecordNotFound, ArgumentError head :not_found end def resolve_model_class(model_param) return nil if model_param.blank? - class_name = model_param.to_s.camelize - # Only allow models that are registered for inplace updates. - unless OpenProject::InplaceEdit::UpdateRegistry.registered?(class_name) - raise ArgumentError, "Unsupported model for inplace edit" - end + model_class = + OpenProject::InplaceEdit::UpdateRegistry.resolve_model_class(model_param) - model_class = class_name.safe_constantize - - # Guard against resolving arbitrary non-ActiveRecord constants. - unless model_class.is_a?(Class) && - defined?(ApplicationRecord) && + unless model_class && model_class < ApplicationRecord && model_class.respond_to?(:visible) - raise ArgumentError, "Model is not an ActiveRecord model" + raise ArgumentError, "Unsupported model for inplace edit" end model_class diff --git a/frontend/src/stimulus/controllers/dynamic/inplace-edit.controller.ts b/frontend/src/stimulus/controllers/dynamic/inplace-edit.controller.ts index e306589242a..94d528da59d 100644 --- a/frontend/src/stimulus/controllers/dynamic/inplace-edit.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/inplace-edit.controller.ts @@ -39,7 +39,7 @@ export default class extends Controller { declare urlValue:string; - async activate() { + async request() { const response = await fetch(this.urlValue, { method: 'GET', headers: { Accept: 'text/vnd.turbo-stream.html' }, diff --git a/lib/open_project/inplace_edit/handlers/default_update.rb b/lib/open_project/inplace_edit/handlers/default_update.rb deleted file mode 100644 index 20d9e92c038..00000000000 --- a/lib/open_project/inplace_edit/handlers/default_update.rb +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true - -#-- copyright -# OpenProject is an open source project management software. -# Copyright (C) the OpenProject GmbH -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License version 3. -# -# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: -# Copyright (C) 2006-2013 Jean-Philippe Lang -# Copyright (C) 2010-2013 the ChiliProject Team -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# See COPYRIGHT and LICENSE files for more details. -#++ - -module OpenProject - module InplaceEdit - module Handlers - class DefaultUpdate - def self.call(model:, params:, user:) - BaseServices::Update.new(model:, user:) - .call(params) - end - end - end - end -end diff --git a/lib/open_project/inplace_edit/update_registry.rb b/lib/open_project/inplace_edit/update_registry.rb index 593d2202f8e..4a9e20f32de 100644 --- a/lib/open_project/inplace_edit/update_registry.rb +++ b/lib/open_project/inplace_edit/update_registry.rb @@ -35,25 +35,30 @@ module OpenProject class << self def register(model_class, handler:, contract:) - @registry[model_class.name] = { + @registry[model_class] = { handler: handler, contract: contract } end - def fetch_handler(model) - entry = @registry.fetch(model.class.name) + def registered?(model_class) + @registry.key?(model_class) + end - entry ? entry[:handler] : OpenProject::InplaceEdit::Handlers::DefaultUpdate + def fetch_handler(model) + entry = @registry[model.class] + entry && entry[:handler] end def fetch_contract(model) - entry = @registry.fetch(model.class.name) + entry = @registry[model.class] entry && entry[:contract] end - def registered?(model_class) - @registry.key?(model_class) + def resolve_model_class(param) + class_name = param.to_s.camelize + + @registry.keys.find { |klass| klass.name == class_name } end end end diff --git a/lookbook/docs/patterns/06-inplace-edit_fields.md.erb b/lookbook/docs/patterns/06-inplace-edit-fields.md.erb similarity index 85% rename from lookbook/docs/patterns/06-inplace-edit_fields.md.erb rename to lookbook/docs/patterns/06-inplace-edit-fields.md.erb index 1e2b24c1f1f..de9663957a6 100644 --- a/lookbook/docs/patterns/06-inplace-edit_fields.md.erb +++ b/lookbook/docs/patterns/06-inplace-edit-fields.md.erb @@ -16,13 +16,6 @@ The InplaceEdit system consists of: - **A generic controller** - **TurboStreams + Stimulus** for lazy loading -Depending on the field type, two strategies are used: - -| Field type | Strategy | -|-------------------------------------------------|----------| -| Simple inputs (text, checkbox) | **Eager Edit (CSS switch)** | -| Complex inputs (RichTextAreas, autocompleteres) | **Lazy Edit (TurboStream)** | - ## Usage ```ruby @@ -64,7 +57,6 @@ end %> **tl;dr**: This component is responsible for: - selecting the correct edit field -- deciding between lazy and eager edit - if needed: rendering the appropriate display field - checking whether the attribute is writable @@ -88,7 +80,7 @@ OpenProject::InplaceEdit::FieldRegistry.register( They may render only the field itself or also include submit and reset buttons. Richer fields such as CkEditors typically render their own action buttons, while simpler fields can rely on outer form handling. -Edit field components may define a `display_class`. If present, this class is used to render the read-only display state. +Edit field components define a `display_class`. This class is used to render the read-only display state. **Simplified example of an `EditFieldComponent`:** ```ruby @@ -134,9 +126,29 @@ end They expose the edit URL via data attributes and typically make the rendered value clickable when the attribute is writable. -Display fields are optional. Not every edit field has or needs a display field. If an edit field component does not define a `display_class`, the `InplaceEditFieldComponent` will always render the edit field. -From a technical perspective, these fields are always in edit mode. There is no Turbo-based replacement between display and edit state. Any display versus edit behavior is handled purely via CSS. -This is useful for simple fields (like standard text inputs) which are thus high performant in switching modes +Display fields are mandatory. There is one default `DisplayFieldComponent` which can be inherited from if needed. E.g the RichTextArea does add some more elements to the HTML: +```ruby +module OpenProject + module Common + module InplaceEditFields + module DisplayFields + class RichTextAreaComponent < DisplayFieldComponent + attr_reader :model, :attribute, :writable + + def call + render(Primer::BaseComponent.new(tag: :div, **display_field_arguments)) do + render(Primer::BaseComponent.new(tag: :div, + classes: "op-uc-container op-uc-container_reduced-headings -multiline")) do + render_display_value + end + end + end + end + end + end + end +end +``` ### Update behaviour diff --git a/spec/components/open_project/common/inplace_edit_field_component_spec.rb b/spec/components/open_project/common/inplace_edit_field_component_spec.rb index 8d309bc3736..d1c06ce2efc 100644 --- a/spec/components/open_project/common/inplace_edit_field_component_spec.rb +++ b/spec/components/open_project/common/inplace_edit_field_component_spec.rb @@ -70,7 +70,7 @@ RSpec.describe OpenProject::Common::InplaceEditFieldComponent, type: :component render_inline(described_class.new(model: project, attribute: :description)) expect(rendered_content) - .to have_css(".op-inplace-edit--display-field") + .to have_css(".op-inplace-edit--display-field.op-inplace-edit--display-field_editable") end it "renders edit field when enforce_edit_mode is true" do @@ -94,17 +94,9 @@ RSpec.describe OpenProject::Common::InplaceEditFieldComponent, type: :component render_inline(described_class.new(model: project, attribute: :description)) expect(rendered_content) - .not_to include("click->inplace-edit#activate") - end - - it "passes disabled=true to the edit field" do - render_inline(described_class.new( - model: project, - attribute: :name - )) - + .not_to include("click->inplace-edit#request") expect(rendered_content) - .to have_field("project[name]", type: :text, disabled: true) + .to have_no_css(".op-inplace-edit--display-field.op-inplace-edit--display-field_editable") end end end diff --git a/spec/components/open_project/common/inplace_edit_fields/display_fields/rich_text_area_component_spec.rb b/spec/components/open_project/common/inplace_edit_fields/display_fields/rich_text_area_component_spec.rb index 1a1d04b5a57..5b727108446 100644 --- a/spec/components/open_project/common/inplace_edit_fields/display_fields/rich_text_area_component_spec.rb +++ b/spec/components/open_project/common/inplace_edit_fields/display_fields/rich_text_area_component_spec.rb @@ -57,7 +57,7 @@ RSpec.describe OpenProject::Common::InplaceEditFields::DisplayFields::RichTextAr ) expect(rendered_content) - .to include("data-action=\"click->inplace-edit#activate\"") + .to include("data-action=\"click->inplace-edit#request\"") end it "adds no inplace-edit stimulus data when not writable" do @@ -70,6 +70,6 @@ RSpec.describe OpenProject::Common::InplaceEditFields::DisplayFields::RichTextAr ) expect(rendered_content) - .not_to include("data-action=\"click->inplace-edit#activate\"") + .not_to include("data-action=\"click->inplace-edit#request\"") end end diff --git a/spec/controllers/inplace_edit_fields_controller_spec.rb b/spec/controllers/inplace_edit_fields_controller_spec.rb index 9035f81993e..6082522ad28 100644 --- a/spec/controllers/inplace_edit_fields_controller_spec.rb +++ b/spec/controllers/inplace_edit_fields_controller_spec.rb @@ -100,6 +100,21 @@ RSpec.describe InplaceEditFieldsController do expect(response.media_type).to eq("text/vnd.turbo-stream.html") end end + + context "when no update handler is registered" do + let(:handler) { nil } + + it "returns 404" do + patch :update, params: { + model: model_param, + id: model.id, + attribute:, + project: { name: "Foo" } + }, format: :turbo_stream + + expect(response).to have_http_status(:not_found) + end + end end describe "POST #reset" do diff --git a/spec/lib/open_project/inplace_edit/update_registry_spec.rb b/spec/lib/open_project/inplace_edit/update_registry_spec.rb index 9d544d4d923..96e52d8ba83 100644 --- a/spec/lib/open_project/inplace_edit/update_registry_spec.rb +++ b/spec/lib/open_project/inplace_edit/update_registry_spec.rb @@ -30,13 +30,17 @@ require "rails_helper" RSpec.describe OpenProject::InplaceEdit::UpdateRegistry do - let(:handler) { instance_double(OpenProject::InplaceEdit::Handlers::DefaultUpdate) } + let(:handler) { instance_double(OpenProject::InplaceEdit::Handlers::ProjectUpdate) } let(:contract) { instance_double(Projects::UpdateContract) } before do described_class.instance_variable_set(:@registry, {}) end + after do + described_class.instance_variable_set(:@registry, {}) + end + describe ".register" do it "registers handler and contract for a model" do described_class.register(Project, handler:, contract:) @@ -50,11 +54,11 @@ RSpec.describe OpenProject::InplaceEdit::UpdateRegistry do it "returns true for registered model" do described_class.register(Project, handler:, contract:) - expect(described_class.registered?("Project")).to be(true) + expect(described_class.registered?(Project)).to be(true) end it "returns false for unregistered model" do - expect(described_class.registered?("Foo")).to be(false) + expect(described_class.registered?(Project)).to be(false) end end end From e4d9ffacd4bf527c409764f91881c6488272c91d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Jan 2026 14:32:26 -0300 Subject: [PATCH 032/293] Bump tar from 7.5.3 to 7.5.7 in /frontend (#21809) Bumps [tar](https://github.com/isaacs/node-tar) from 7.5.3 to 7.5.7. - [Release notes](https://github.com/isaacs/node-tar/releases) - [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md) - [Commits](https://github.com/isaacs/node-tar/compare/v7.5.3...v7.5.7) --- updated-dependencies: - dependency-name: tar dependency-version: 7.5.7 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- frontend/package-lock.json | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 3870447a3c9..1668e106ff0 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -23528,10 +23528,9 @@ } }, "node_modules/tar": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.3.tgz", - "integrity": "sha512-ENg5JUHUm2rDD7IvKNFGzyElLXNjachNLp6RaGf4+JOgxXHkqA+gq81ZAMCUmtMtqBsoU62lcp6S27g1LCYGGQ==", - "license": "BlueOak-1.0.0", + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz", + "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", @@ -41153,9 +41152,9 @@ "dev": true }, "tar": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.3.tgz", - "integrity": "sha512-ENg5JUHUm2rDD7IvKNFGzyElLXNjachNLp6RaGf4+JOgxXHkqA+gq81ZAMCUmtMtqBsoU62lcp6S27g1LCYGGQ==", + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz", + "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==", "requires": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", From 22f84cea860268be67d79b85c9e8a2bcd34cb1f0 Mon Sep 17 00:00:00 2001 From: OpenProject Actions CI Date: Fri, 30 Jan 2026 03:49:34 +0000 Subject: [PATCH 033/293] update locales from crowdin [ci skip] --- config/locales/crowdin/af.yml | 1 + config/locales/crowdin/ar.yml | 1 + config/locales/crowdin/az.yml | 1 + config/locales/crowdin/be.yml | 1 + config/locales/crowdin/bg.yml | 1 + config/locales/crowdin/ca.yml | 1 + config/locales/crowdin/ckb-IR.yml | 1 + config/locales/crowdin/cs.yml | 1 + config/locales/crowdin/da.yml | 1 + config/locales/crowdin/de.yml | 1 + config/locales/crowdin/el.yml | 1 + config/locales/crowdin/eo.yml | 1 + config/locales/crowdin/es.yml | 1 + config/locales/crowdin/et.yml | 1 + config/locales/crowdin/eu.yml | 1 + config/locales/crowdin/fa.yml | 1 + config/locales/crowdin/fi.yml | 1 + config/locales/crowdin/fil.yml | 1 + config/locales/crowdin/fr.yml | 1 + config/locales/crowdin/he.yml | 1 + config/locales/crowdin/hi.yml | 1 + config/locales/crowdin/hr.yml | 1 + config/locales/crowdin/hu.yml | 1 + config/locales/crowdin/id.yml | 1 + config/locales/crowdin/it.yml | 1 + config/locales/crowdin/ja.yml | 1 + config/locales/crowdin/js-id.yml | 32 +- config/locales/crowdin/ka.yml | 1 + config/locales/crowdin/kk.yml | 1 + config/locales/crowdin/ko.yml | 1 + config/locales/crowdin/lt.yml | 1 + config/locales/crowdin/lv.yml | 1 + config/locales/crowdin/mn.yml | 1 + config/locales/crowdin/ms.yml | 1 + config/locales/crowdin/ne.yml | 1 + config/locales/crowdin/nl.yml | 1 + config/locales/crowdin/no.yml | 1 + config/locales/crowdin/pl.yml | 1 + config/locales/crowdin/pt-BR.yml | 1 + config/locales/crowdin/pt-PT.yml | 1 + config/locales/crowdin/ro.yml | 1 + config/locales/crowdin/ru.yml | 1 + config/locales/crowdin/rw.yml | 1 + config/locales/crowdin/si.yml | 1 + config/locales/crowdin/sk.yml | 1 + config/locales/crowdin/sl.yml | 1 + config/locales/crowdin/sr.yml | 1 + config/locales/crowdin/sv.yml | 1 + config/locales/crowdin/th.yml | 1 + config/locales/crowdin/tr.yml | 1 + config/locales/crowdin/uk.yml | 1 + config/locales/crowdin/uz.yml | 1 + config/locales/crowdin/vi.yml | 1 + config/locales/crowdin/zh-CN.yml | 1 + config/locales/crowdin/zh-TW.yml | 21 +- .../auth_saml/config/locales/crowdin/id.yml | 2 +- modules/avatars/config/locales/crowdin/id.yml | 2 +- .../backlogs/config/locales/crowdin/id.yml | 4 +- .../bim/config/locales/crowdin/id.seeders.yml | 28 +- modules/bim/config/locales/crowdin/id.yml | 4 +- modules/boards/config/locales/crowdin/id.yml | 4 +- modules/budgets/config/locales/crowdin/id.yml | 2 +- .../calendar/config/locales/crowdin/id.yml | 4 +- modules/costs/config/locales/crowdin/id.yml | 6 +- .../documents/config/locales/crowdin/id.yml | 22 +- modules/gantt/config/locales/crowdin/id.yml | 2 +- .../config/locales/crowdin/id.yml | 6 +- .../config/locales/crowdin/js-id.yml | 20 +- .../job_status/config/locales/crowdin/id.yml | 4 +- .../ldap_groups/config/locales/crowdin/id.yml | 4 +- modules/meeting/config/locales/crowdin/id.yml | 426 +++++++++--------- .../meeting/config/locales/crowdin/js-ru.yml | 2 +- modules/meeting/config/locales/crowdin/ru.yml | 16 +- .../meeting/config/locales/crowdin/zh-TW.yml | 16 +- .../config/locales/crowdin/id.yml | 60 +-- .../recaptcha/config/locales/crowdin/id.yml | 4 +- .../reporting/config/locales/crowdin/id.yml | 12 +- .../config/locales/crowdin/js-id.yml | 2 +- .../storages/config/locales/crowdin/zh-TW.yml | 22 +- .../config/locales/crowdin/id.yml | 4 +- .../config/locales/crowdin/js-id.yml | 36 +- .../config/locales/crowdin/id.yml | 46 +- .../config/locales/crowdin/js-id.yml | 2 +- .../webhooks/config/locales/crowdin/id.yml | 10 +- 84 files changed, 466 insertions(+), 412 deletions(-) diff --git a/config/locales/crowdin/af.yml b/config/locales/crowdin/af.yml index 9aa1b98cf34..828893abc94 100644 --- a/config/locales/crowdin/af.yml +++ b/config/locales/crowdin/af.yml @@ -1494,6 +1494,7 @@ af: even: "must be even." exclusion: "is reserved." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "is too large (maximum size is %{count} Bytes)." filter_does_not_exist: "filter does not exist." format: "does not match the expected format '%{expected}'." diff --git a/config/locales/crowdin/ar.yml b/config/locales/crowdin/ar.yml index 719e46387a7..78db0c0b0b2 100644 --- a/config/locales/crowdin/ar.yml +++ b/config/locales/crowdin/ar.yml @@ -1530,6 +1530,7 @@ ar: even: "يجب أن يكون زوجي." exclusion: "محجوز." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "إنه كبير جدا(الحجم الأكبر هو%{count} بايت)." filter_does_not_exist: "filter does not exist." format: "does not match the expected format '%{expected}'." diff --git a/config/locales/crowdin/az.yml b/config/locales/crowdin/az.yml index da50c13a0b2..e8b7be2794a 100644 --- a/config/locales/crowdin/az.yml +++ b/config/locales/crowdin/az.yml @@ -1494,6 +1494,7 @@ az: even: "must be even." exclusion: "is reserved." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "is too large (maximum size is %{count} Bytes)." filter_does_not_exist: "filter does not exist." format: "does not match the expected format '%{expected}'." diff --git a/config/locales/crowdin/be.yml b/config/locales/crowdin/be.yml index 22f8a88af3c..26ba0fdd672 100644 --- a/config/locales/crowdin/be.yml +++ b/config/locales/crowdin/be.yml @@ -1512,6 +1512,7 @@ be: even: "must be even." exclusion: "is reserved." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "is too large (maximum size is %{count} Bytes)." filter_does_not_exist: "filter does not exist." format: "does not match the expected format '%{expected}'." diff --git a/config/locales/crowdin/bg.yml b/config/locales/crowdin/bg.yml index 1b3d88dcbe2..c9bc83add3f 100644 --- a/config/locales/crowdin/bg.yml +++ b/config/locales/crowdin/bg.yml @@ -1494,6 +1494,7 @@ bg: even: "трябва да бъде четно число." exclusion: "е запазено." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "е твърде голям (максимален размер %{count} байта)." filter_does_not_exist: "филтърът не съществува." format: "не съответства на очаквания формат '%{expected}'." diff --git a/config/locales/crowdin/ca.yml b/config/locales/crowdin/ca.yml index 1e4b42c0d9f..db8384813a1 100644 --- a/config/locales/crowdin/ca.yml +++ b/config/locales/crowdin/ca.yml @@ -1491,6 +1491,7 @@ ca: even: "ha de ser parell." exclusion: "està reservat." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "és massa gran (la mida màxima és de %{count} Bytes)." filter_does_not_exist: "el filtre no existeix." format: "no coincideix amb el format esperat '%{expected}'." diff --git a/config/locales/crowdin/ckb-IR.yml b/config/locales/crowdin/ckb-IR.yml index 824332bd214..d74593eb569 100644 --- a/config/locales/crowdin/ckb-IR.yml +++ b/config/locales/crowdin/ckb-IR.yml @@ -1494,6 +1494,7 @@ ckb-IR: even: "must be even." exclusion: "is reserved." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "is too large (maximum size is %{count} Bytes)." filter_does_not_exist: "filter does not exist." format: "does not match the expected format '%{expected}'." diff --git a/config/locales/crowdin/cs.yml b/config/locales/crowdin/cs.yml index 91ab2a3b171..1b9760de5ee 100644 --- a/config/locales/crowdin/cs.yml +++ b/config/locales/crowdin/cs.yml @@ -1512,6 +1512,7 @@ cs: even: "musí být sudé." exclusion: "vyhrazeno." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "je příliš velký (maximální velikost je %{count} Bajtů)." filter_does_not_exist: "Filtr neexistuje." format: "neodpovídá očekávanému formátu '%{expected}'." diff --git a/config/locales/crowdin/da.yml b/config/locales/crowdin/da.yml index 0864714f00c..193630a0f90 100644 --- a/config/locales/crowdin/da.yml +++ b/config/locales/crowdin/da.yml @@ -1492,6 +1492,7 @@ da: even: "skal være lige." exclusion: "er reserveret." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "er for stor (maks. størrelse er %{count} Bytes)." filter_does_not_exist: "filter does not exist." format: "does not match the expected format '%{expected}'." diff --git a/config/locales/crowdin/de.yml b/config/locales/crowdin/de.yml index b845642d162..b37a0a9d6a6 100644 --- a/config/locales/crowdin/de.yml +++ b/config/locales/crowdin/de.yml @@ -1486,6 +1486,7 @@ de: even: "muss gerade sein." exclusion: "ist nicht verfügbar." feature_disabled: ist nicht verfügbar. + feature_disabled_for_project: is disabled for this project. file_too_large: "ist zu groß (nicht mehr als %{count} Bytes erlaubt)." filter_does_not_exist: "Filter existiert nicht." format: "stimmt nicht mit dem erwarteten Format '%{expected} ' überein." diff --git a/config/locales/crowdin/el.yml b/config/locales/crowdin/el.yml index bf131daac9d..e5db9e5910a 100644 --- a/config/locales/crowdin/el.yml +++ b/config/locales/crowdin/el.yml @@ -1490,6 +1490,7 @@ el: even: "πρέπει να είναι άρτιος." exclusion: "είναι δεσμευμένο." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "είναι πολύ μεγάλο (το μέγιστο μέγεθος είναι %{count} Bytes)." filter_does_not_exist: "filter does not exist." format: "does not match the expected format '%{expected}'." diff --git a/config/locales/crowdin/eo.yml b/config/locales/crowdin/eo.yml index 6da09e02c59..f30c38f4136 100644 --- a/config/locales/crowdin/eo.yml +++ b/config/locales/crowdin/eo.yml @@ -1494,6 +1494,7 @@ eo: even: "must be even." exclusion: "rezervita." feature_disabled: ne estas disponebla. + feature_disabled_for_project: is disabled for this project. file_too_large: "is too large (maximum size is %{count} Bytes)." filter_does_not_exist: "filter does not exist." format: "does not match the expected format '%{expected}'." diff --git a/config/locales/crowdin/es.yml b/config/locales/crowdin/es.yml index 001b5731bc6..30b2acdc079 100644 --- a/config/locales/crowdin/es.yml +++ b/config/locales/crowdin/es.yml @@ -1491,6 +1491,7 @@ es: even: "debe ser incluido." exclusion: "está reservado." feature_disabled: no está disponible. + feature_disabled_for_project: is disabled for this project. file_too_large: "es demasiado grande (el tamaño máximo es de %{count} Bytes)." filter_does_not_exist: "el filtro no existe." format: "no coincide con el formato esperado '%{expected}'." diff --git a/config/locales/crowdin/et.yml b/config/locales/crowdin/et.yml index 33eab51949b..a2a3b44e597 100644 --- a/config/locales/crowdin/et.yml +++ b/config/locales/crowdin/et.yml @@ -1494,6 +1494,7 @@ et: even: "peab olema paarisarv." exclusion: "on reserveeritud." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "is too large (maximum size is %{count} Bytes)." filter_does_not_exist: "filter does not exist." format: "does not match the expected format '%{expected}'." diff --git a/config/locales/crowdin/eu.yml b/config/locales/crowdin/eu.yml index 0265242dcd0..7c8570b0a28 100644 --- a/config/locales/crowdin/eu.yml +++ b/config/locales/crowdin/eu.yml @@ -1494,6 +1494,7 @@ eu: even: "must be even." exclusion: "is reserved." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "is too large (maximum size is %{count} Bytes)." filter_does_not_exist: "filter does not exist." format: "does not match the expected format '%{expected}'." diff --git a/config/locales/crowdin/fa.yml b/config/locales/crowdin/fa.yml index b89e81e8a71..5f6c4480f9e 100644 --- a/config/locales/crowdin/fa.yml +++ b/config/locales/crowdin/fa.yml @@ -1494,6 +1494,7 @@ fa: even: "must be even." exclusion: "is reserved." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "is too large (maximum size is %{count} Bytes)." filter_does_not_exist: "filter does not exist." format: "does not match the expected format '%{expected}'." diff --git a/config/locales/crowdin/fi.yml b/config/locales/crowdin/fi.yml index 797b5d7c1ac..18796bc4f32 100644 --- a/config/locales/crowdin/fi.yml +++ b/config/locales/crowdin/fi.yml @@ -1494,6 +1494,7 @@ fi: even: "täytyy olla parillinen." exclusion: "on jo varattu." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "on liian suuri (suurin sallittu koko on %{count} tavua)." filter_does_not_exist: "filter does not exist." format: "does not match the expected format '%{expected}'." diff --git a/config/locales/crowdin/fil.yml b/config/locales/crowdin/fil.yml index ec349875ade..4f169053da9 100644 --- a/config/locales/crowdin/fil.yml +++ b/config/locales/crowdin/fil.yml @@ -1494,6 +1494,7 @@ fil: even: "dapat ay kapareho." exclusion: "ay nakareserba." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "ay masyadong malaki (pinakamataas na laki ay %{count} Bytes)." filter_does_not_exist: "filter does not exist." format: "does not match the expected format '%{expected}'." diff --git a/config/locales/crowdin/fr.yml b/config/locales/crowdin/fr.yml index d4e2d5a7e97..baadf3df3c3 100644 --- a/config/locales/crowdin/fr.yml +++ b/config/locales/crowdin/fr.yml @@ -1492,6 +1492,7 @@ fr: even: "doit être pair." exclusion: "est réservé." feature_disabled: n'est pas disponible. + feature_disabled_for_project: is disabled for this project. file_too_large: "est trop volumineux (la taille maximale est de %{count} octets)." filter_does_not_exist: "le filtre n'existe pas." format: "ne correspond pas au format attendu « %{expected} »." diff --git a/config/locales/crowdin/he.yml b/config/locales/crowdin/he.yml index bcadb0e8022..112ae045710 100644 --- a/config/locales/crowdin/he.yml +++ b/config/locales/crowdin/he.yml @@ -1512,6 +1512,7 @@ he: even: "must be even." exclusion: "is reserved." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "is too large (maximum size is %{count} Bytes)." filter_does_not_exist: "filter does not exist." format: "does not match the expected format '%{expected}'." diff --git a/config/locales/crowdin/hi.yml b/config/locales/crowdin/hi.yml index 423eab90139..2f85b49e2c0 100644 --- a/config/locales/crowdin/hi.yml +++ b/config/locales/crowdin/hi.yml @@ -1492,6 +1492,7 @@ hi: even: "must be even." exclusion: "is reserved." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "is too large (maximum size is %{count} Bytes)." filter_does_not_exist: "filter does not exist." format: "does not match the expected format '%{expected}'." diff --git a/config/locales/crowdin/hr.yml b/config/locales/crowdin/hr.yml index d1ccc5b2580..940c01c39bb 100644 --- a/config/locales/crowdin/hr.yml +++ b/config/locales/crowdin/hr.yml @@ -1503,6 +1503,7 @@ hr: even: "mora biti izjednačeno." exclusion: "je rezerviran." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "je prevelik (maksimalna veličina iznosi %{count} bajta)." filter_does_not_exist: "filter does not exist." format: "does not match the expected format '%{expected}'." diff --git a/config/locales/crowdin/hu.yml b/config/locales/crowdin/hu.yml index 6b0cc98fdba..0e0d8c34370 100644 --- a/config/locales/crowdin/hu.yml +++ b/config/locales/crowdin/hu.yml @@ -1493,6 +1493,7 @@ hu: even: "kell még." exclusion: "foglalt." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "túl nagy fájlméret (maximális méret %{count} Byte)." filter_does_not_exist: "szűrő nem létezik." format: "nem felel meg az elvárt '%{expected}' formátumnak." diff --git a/config/locales/crowdin/id.yml b/config/locales/crowdin/id.yml index a41dfce0c70..44ee27c1f87 100644 --- a/config/locales/crowdin/id.yml +++ b/config/locales/crowdin/id.yml @@ -1481,6 +1481,7 @@ id: even: "harus imbang." exclusion: "telah dipesan." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "ukuran maksimum yang diperbolehkan %{count} Bytes." filter_does_not_exist: "filter tidak ada." format: "tidak cocok dengan format yang diharapkan '%{expected}'." diff --git a/config/locales/crowdin/it.yml b/config/locales/crowdin/it.yml index 0f06bcaa1c2..fc99abc381a 100644 --- a/config/locales/crowdin/it.yml +++ b/config/locales/crowdin/it.yml @@ -1491,6 +1491,7 @@ it: even: "deve essere pari." exclusion: "è riservato." feature_disabled: non disponibile. + feature_disabled_for_project: is disabled for this project. file_too_large: "è troppo grande (la dimensione massima è %{count} Byte)." filter_does_not_exist: "il filtro non esiste" format: "non corrisponde al formato previsto '%{expected}'." diff --git a/config/locales/crowdin/ja.yml b/config/locales/crowdin/ja.yml index 5c15bbdbecd..bad61bb62b4 100644 --- a/config/locales/crowdin/ja.yml +++ b/config/locales/crowdin/ja.yml @@ -1484,6 +1484,7 @@ ja: even: "は偶数にしてください。" exclusion: "は予約されています。" feature_disabled: は利用できません。 + feature_disabled_for_project: is disabled for this project. file_too_large: "は大きすぎます (最大サイズは%{count}バイト)" filter_does_not_exist: "フィルターが存在しません。" format: "期待されるフォーマットの「%{expected}」と一致しません。" diff --git a/config/locales/crowdin/js-id.yml b/config/locales/crowdin/js-id.yml index 464b18a160d..6930060da80 100644 --- a/config/locales/crowdin/js-id.yml +++ b/config/locales/crowdin/js-id.yml @@ -216,10 +216,10 @@ id: change_button: "Save and reschedule" change_title: "Ubah hari kerja" removed_title: "You will remove the following days from the non-working days list:" - change_description: "Changing which days of the week are considered working days or non-working days can affect the start and finish days of all work packages and life cycles in all projects in this instance." + change_description: "Perubahan hari dalam seminggu yang dianggap sebagai hari kerja atau hari libur dapat memengaruhi tanggal mulai dan selesai dari semua paket kerja dan siklus hidup dalam semua proyek pada kasus ini." warning: > - The changes might take some time to take effect. You will be notified when all relevant work packages and project life cycles have been updated. - Are you sure you want to continue? + Perubahan tersebut mungkin memerlukan waktu untuk berlaku. Anda akan diberitahu ketika semua paket kerja yang relevan dan siklus hidup proyek telah diperbarui. + Apakah Anda yakin ingin melanjutkan? work_packages_settings: warning_progress_calculation_mode_change_from_status_to_field_html: >- Changing progress calculation mode from status-based to work-based will make the % Complete field freely editable. If you optionally enter values for Work or Remaining work, they will also be linked to % Complete. Changing Remaining work can then update % Complete. @@ -558,11 +558,11 @@ id: date_alerts: milestone_date: "Milestone date" overdue: "Overdue" - overdue_since: "for %{difference_in_days}." - property_today: "is today." - property_is: "is in %{difference_in_days}." - property_was: "was %{difference_in_days} ago." - property_is_deleted: "is deleted." + overdue_since: "untuk %{difference_in_days}." + property_today: "hari ini." + property_is: "dalam %{difference_in_days}." + property_was: "%{difference_in_days} hari yang lalu." + property_is_deleted: "telah dihapus." center: label_actor_and: "dan" and_more_users: @@ -635,8 +635,8 @@ id: pagination: no_other_page: "You are on the only page." pages_skipped: "Pages skipped." - page_navigation: "Pagination navigation" - per_page_navigation: 'Items per page selection' + page_navigation: "Navigasi halaman" + per_page_navigation: 'Pilihan jumlah item per halaman' pages: page_number: Page %{number} show_per_page: Show %{number} per page @@ -763,7 +763,7 @@ id: update_relation: "Klik untuk mengganti jenis hubungan" show_relations: "Show relations" add_predecessor: "Tambahkan pendahulu" - add_successor: "Add successor" + add_successor: "Tambahkan penerus" remove: "Remove relation" save: "Save relation" abort: "Abort" @@ -821,7 +821,7 @@ id: bulk_actions: edit: "Edit massal" delete: "Penghapusan massal" - duplicate: "Bulk duplicate" + duplicate: "Duplikat massal" move: "Bulk change of project" button_clear: "Clear" comment_added: "The comment was successfully added." @@ -867,7 +867,7 @@ id: header_with_parent: "New %{type} (Child of %{parent_type} #%{id})" button: "Buat baru" duplicate: - title: "Duplicate work package" + title: "Duplikat paket kerja" hierarchy: show: "Menampilkan modus hiraki" hide: "Sembunyikan modus hirarki" @@ -978,7 +978,7 @@ id: is_switched_from_manual_to_automatic: "The dates of this work package may need to be recalculated after switching from manual to automatic scheduling due to relationships with other work packages." sharing: title: "Share work package" - show_all_users: "Show all users with whom the work package has been shared with" + show_all_users: "Tampilkan semua pengguna yang telah dibagikan paket kerja ini" table: configure_button: "Configure work package table" summary: "Table with rows of work package and columns of work package attributes." @@ -1153,7 +1153,7 @@ id: selected_filter: all: "Semua proyek" selected: "Only selected" - search_placeholder: "Search projects..." + search_placeholder: "Cari proyek..." search_placeholder_favorites: "Search favorites..." include_subprojects: "Include all sub-projects" tooltip: @@ -1210,7 +1210,7 @@ id: close: "Close modal" open_project_storage_modal: waiting_title: - timeout: "Timeout" + timeout: "Waktu habis" waiting_subtitle: network_off: "There is a network problem." network_on: "Network is back. We are trying." diff --git a/config/locales/crowdin/ka.yml b/config/locales/crowdin/ka.yml index 5c1ec5254f1..7c812cdf18f 100644 --- a/config/locales/crowdin/ka.yml +++ b/config/locales/crowdin/ka.yml @@ -1494,6 +1494,7 @@ ka: even: "ლუწი უნდა იყოს" exclusion: "დაცულია." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "is too large (maximum size is %{count} Bytes)." filter_does_not_exist: "filter does not exist." format: "does not match the expected format '%{expected}'." diff --git a/config/locales/crowdin/kk.yml b/config/locales/crowdin/kk.yml index 0696b8ba1f3..e90a39cd5b7 100644 --- a/config/locales/crowdin/kk.yml +++ b/config/locales/crowdin/kk.yml @@ -1494,6 +1494,7 @@ kk: even: "must be even." exclusion: "is reserved." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "is too large (maximum size is %{count} Bytes)." filter_does_not_exist: "filter does not exist." format: "does not match the expected format '%{expected}'." diff --git a/config/locales/crowdin/ko.yml b/config/locales/crowdin/ko.yml index 2ce8bcd9acc..840048b237b 100644 --- a/config/locales/crowdin/ko.yml +++ b/config/locales/crowdin/ko.yml @@ -1485,6 +1485,7 @@ ko: even: "에 짝수를 입력해 주세요" exclusion: "예약됨" feature_disabled: '- 사용할 수 없습니다.' + feature_disabled_for_project: is disabled for this project. file_too_large: "은(는) 너무 큽니다. (최대 %{count} 바이트)" filter_does_not_exist: "필터가 존재하지 않습니다." format: "- 필요한 형식 '%{expected}'과(와) 일치하지 않습니다." diff --git a/config/locales/crowdin/lt.yml b/config/locales/crowdin/lt.yml index de2fb0537d3..3369cb69324 100644 --- a/config/locales/crowdin/lt.yml +++ b/config/locales/crowdin/lt.yml @@ -1509,6 +1509,7 @@ lt: even: "turi būti lyginis." exclusion: "yra rezervuotas." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "per didelis (didžiausias leistinas dydis yra %{count} baitų)." filter_does_not_exist: "filtras neegzistuoja." format: "neatitinka laukiamo formato '%{expected}'." diff --git a/config/locales/crowdin/lv.yml b/config/locales/crowdin/lv.yml index 2d6ef3f8c7a..b5322f3d039 100644 --- a/config/locales/crowdin/lv.yml +++ b/config/locales/crowdin/lv.yml @@ -1503,6 +1503,7 @@ lv: even: "jābūt vienlīdzīgiem." exclusion: "rezervēts." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "ir pārāk liels (maksimālais lielums ir %{count} baiti)." filter_does_not_exist: "filter does not exist." format: "does not match the expected format '%{expected}'." diff --git a/config/locales/crowdin/mn.yml b/config/locales/crowdin/mn.yml index 0f72613b61f..b568a464246 100644 --- a/config/locales/crowdin/mn.yml +++ b/config/locales/crowdin/mn.yml @@ -1494,6 +1494,7 @@ mn: even: "must be even." exclusion: "is reserved." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "is too large (maximum size is %{count} Bytes)." filter_does_not_exist: "filter does not exist." format: "does not match the expected format '%{expected}'." diff --git a/config/locales/crowdin/ms.yml b/config/locales/crowdin/ms.yml index d0ba5550b89..3f3c594397a 100644 --- a/config/locales/crowdin/ms.yml +++ b/config/locales/crowdin/ms.yml @@ -1483,6 +1483,7 @@ ms: even: "perlu sama." exclusion: "sudah dikhaskan." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "adalah terlalu besar (saiz maksimum adalah %{count} Bytes)." filter_does_not_exist: "penyaring tidak wujud." format: "tidak sepadan dengan format yang dijangka '%{expected}'." diff --git a/config/locales/crowdin/ne.yml b/config/locales/crowdin/ne.yml index e23818ed999..21b4af02c69 100644 --- a/config/locales/crowdin/ne.yml +++ b/config/locales/crowdin/ne.yml @@ -1494,6 +1494,7 @@ ne: even: "must be even." exclusion: "is reserved." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "is too large (maximum size is %{count} Bytes)." filter_does_not_exist: "filter does not exist." format: "does not match the expected format '%{expected}'." diff --git a/config/locales/crowdin/nl.yml b/config/locales/crowdin/nl.yml index cb1a1bd117a..f7152d3f92f 100644 --- a/config/locales/crowdin/nl.yml +++ b/config/locales/crowdin/nl.yml @@ -1490,6 +1490,7 @@ nl: even: "moet gelijk zijn." exclusion: "is gereserveerd." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "is te groot (maximum grootte is %{count} Bytes)." filter_does_not_exist: "filter bestaat niet." format: "komt niet overeen met het verwachte formaat '%{expected}' '." diff --git a/config/locales/crowdin/no.yml b/config/locales/crowdin/no.yml index a2933c8905a..0c007478a66 100644 --- a/config/locales/crowdin/no.yml +++ b/config/locales/crowdin/no.yml @@ -1493,6 +1493,7 @@ even: "må være partall" exclusion: "er reservert." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "er for stor (maks størrelse er %{count} Bytes)." filter_does_not_exist: "filter finnes ikke." format: "samsvarer ikke med det forventede formatet '%{expected}'." diff --git a/config/locales/crowdin/pl.yml b/config/locales/crowdin/pl.yml index d072d7d5432..ab8bb622734 100644 --- a/config/locales/crowdin/pl.yml +++ b/config/locales/crowdin/pl.yml @@ -1508,6 +1508,7 @@ pl: even: "musi być takie samo." exclusion: "jest już zajęte." feature_disabled: jest niedostępny. + feature_disabled_for_project: is disabled for this project. file_too_large: "jest za długie (maksymalna wielkość to %{count} Bajtów)." filter_does_not_exist: "filtr nie istnieje." format: "nie pasuje do oczekiwanego formatu '%{expected}'." diff --git a/config/locales/crowdin/pt-BR.yml b/config/locales/crowdin/pt-BR.yml index fe59f128030..35df94d07e2 100644 --- a/config/locales/crowdin/pt-BR.yml +++ b/config/locales/crowdin/pt-BR.yml @@ -1491,6 +1491,7 @@ pt-BR: even: "deve ser par." exclusion: "está reservado." feature_disabled: não está disponível. + feature_disabled_for_project: is disabled for this project. file_too_large: "é muito grande (tamanho máximo é %{count} Bytes)." filter_does_not_exist: "filtro não existe." format: "não corresponde ao formato '%{expected}' esperado." diff --git a/config/locales/crowdin/pt-PT.yml b/config/locales/crowdin/pt-PT.yml index d394effcfd1..56a9d79d82c 100644 --- a/config/locales/crowdin/pt-PT.yml +++ b/config/locales/crowdin/pt-PT.yml @@ -1491,6 +1491,7 @@ pt-PT: even: "deve ser par." exclusion: "é reservado." feature_disabled: não está disponível. + feature_disabled_for_project: is disabled for this project. file_too_large: "é muito grande (tamanho máximo é %{count} Bytes)." filter_does_not_exist: "filtro não existe." format: "não corresponde ao formato esperado '%{expected}'." diff --git a/config/locales/crowdin/ro.yml b/config/locales/crowdin/ro.yml index b1c3452c0d3..67970a3d530 100644 --- a/config/locales/crowdin/ro.yml +++ b/config/locales/crowdin/ro.yml @@ -1503,6 +1503,7 @@ ro: even: "trebuie să fie par." exclusion: "este rezervat." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "este prea mare (dimensiunea maximă este %{count} Octeți)." filter_does_not_exist: "filtrul nu există." format: "nu se potrivește cu formatul așteptat '%{expected}'." diff --git a/config/locales/crowdin/ru.yml b/config/locales/crowdin/ru.yml index e902c142745..c02b0be4a61 100644 --- a/config/locales/crowdin/ru.yml +++ b/config/locales/crowdin/ru.yml @@ -1510,6 +1510,7 @@ ru: even: "должно быть чётным." exclusion: "зарезервировано." feature_disabled: недоступно. + feature_disabled_for_project: is disabled for this project. file_too_large: "слишком большой (максимальный размер составляет %{count} байт)." filter_does_not_exist: "фильтр не существует." format: "не соответствует ожидаемому формату '%{expected}'." diff --git a/config/locales/crowdin/rw.yml b/config/locales/crowdin/rw.yml index cd0c53c0f35..040c0588061 100644 --- a/config/locales/crowdin/rw.yml +++ b/config/locales/crowdin/rw.yml @@ -1494,6 +1494,7 @@ rw: even: "must be even." exclusion: "is reserved." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "is too large (maximum size is %{count} Bytes)." filter_does_not_exist: "filter does not exist." format: "does not match the expected format '%{expected}'." diff --git a/config/locales/crowdin/si.yml b/config/locales/crowdin/si.yml index 5e5892c0bed..a88c1e4e7ea 100644 --- a/config/locales/crowdin/si.yml +++ b/config/locales/crowdin/si.yml @@ -1494,6 +1494,7 @@ si: even: "පවා විය යුතුය." exclusion: "වෙන් කර ඇත." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "ඉතා විශාලයි (උපරිම ප්රමාණය බයිට් %{count} කි)." filter_does_not_exist: "filter does not exist." format: "does not match the expected format '%{expected}'." diff --git a/config/locales/crowdin/sk.yml b/config/locales/crowdin/sk.yml index e88816e1d94..674ef920a78 100644 --- a/config/locales/crowdin/sk.yml +++ b/config/locales/crowdin/sk.yml @@ -1512,6 +1512,7 @@ sk: even: "musí byť párne." exclusion: "patrí medzi vyhradené výrazy." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "je príliš veľká (maximálna veľkosť je %{count} bytov)." filter_does_not_exist: "filter does not exist." format: "does not match the expected format '%{expected}'." diff --git a/config/locales/crowdin/sl.yml b/config/locales/crowdin/sl.yml index 83bf515158f..24a5b6878d1 100644 --- a/config/locales/crowdin/sl.yml +++ b/config/locales/crowdin/sl.yml @@ -1511,6 +1511,7 @@ sl: even: "mora biti enakomerno." exclusion: "je rezervirano." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "je preveliko (največja velikost je %{count} Bytov)." filter_does_not_exist: "filter does not exist." format: "se ne ujema s pričakovano obliko '%{expected}'." diff --git a/config/locales/crowdin/sr.yml b/config/locales/crowdin/sr.yml index 89ff6ebebb4..009a07a2d03 100644 --- a/config/locales/crowdin/sr.yml +++ b/config/locales/crowdin/sr.yml @@ -1503,6 +1503,7 @@ sr: even: "must be even." exclusion: "is reserved." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "is too large (maximum size is %{count} Bytes)." filter_does_not_exist: "filter does not exist." format: "does not match the expected format '%{expected}'." diff --git a/config/locales/crowdin/sv.yml b/config/locales/crowdin/sv.yml index 9153ba66ccb..b8b73650f5a 100644 --- a/config/locales/crowdin/sv.yml +++ b/config/locales/crowdin/sv.yml @@ -1494,6 +1494,7 @@ sv: even: "måste vara jämn." exclusion: "är reserverat." feature_disabled: är inte tillgänglig. + feature_disabled_for_project: is disabled for this project. file_too_large: "är för stor (största storleken är %{count} Bytes)." filter_does_not_exist: "filter does not exist." format: "does not match the expected format '%{expected}'." diff --git a/config/locales/crowdin/th.yml b/config/locales/crowdin/th.yml index 520fc5035e2..24b6d57e413 100644 --- a/config/locales/crowdin/th.yml +++ b/config/locales/crowdin/th.yml @@ -1485,6 +1485,7 @@ th: even: "must be even." exclusion: "is reserved." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "ใหญ่เกินไป (ขนาดสูงสุดคือ %{count} ไบต์)" filter_does_not_exist: "filter does not exist." format: "does not match the expected format '%{expected}'." diff --git a/config/locales/crowdin/tr.yml b/config/locales/crowdin/tr.yml index f15eee6c730..55fd6063dc8 100644 --- a/config/locales/crowdin/tr.yml +++ b/config/locales/crowdin/tr.yml @@ -1494,6 +1494,7 @@ tr: even: "çift olmalı." exclusion: "ayrılmıştır." feature_disabled: mevcut değil. + feature_disabled_for_project: is disabled for this project. file_too_large: "çok büyük (en büyük boyut %{count} bayt)." filter_does_not_exist: "Filtre mevcut değil" format: "Beklenen biçim '%{expected}' ile eşleşmiyor." diff --git a/config/locales/crowdin/uk.yml b/config/locales/crowdin/uk.yml index 42ca69a997f..1010e617be2 100644 --- a/config/locales/crowdin/uk.yml +++ b/config/locales/crowdin/uk.yml @@ -1506,6 +1506,7 @@ uk: even: "має бути рівним." exclusion: "зарезервовано." feature_disabled: недоступна. + feature_disabled_for_project: is disabled for this project. file_too_large: "занадто великий (максимальний розмір -%{count} байт)" filter_does_not_exist: "фільтр не існує." format: "не відповідає очікуваному формату «%{expected}»." diff --git a/config/locales/crowdin/uz.yml b/config/locales/crowdin/uz.yml index 1f18c034e34..25cdaae6109 100644 --- a/config/locales/crowdin/uz.yml +++ b/config/locales/crowdin/uz.yml @@ -1494,6 +1494,7 @@ uz: even: "must be even." exclusion: "is reserved." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "is too large (maximum size is %{count} Bytes)." filter_does_not_exist: "filter does not exist." format: "does not match the expected format '%{expected}'." diff --git a/config/locales/crowdin/vi.yml b/config/locales/crowdin/vi.yml index 70575755ece..ac4ab227487 100644 --- a/config/locales/crowdin/vi.yml +++ b/config/locales/crowdin/vi.yml @@ -1485,6 +1485,7 @@ vi: even: "phải là số chẵn." exclusion: "được bảo lưu." feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. file_too_large: "quá lớn (kích thước tối đa là %{count} bytes)" filter_does_not_exist: "bộ lọc không tồn tại." format: "không khớp với định dạng mong đợi '%{expected}'." diff --git a/config/locales/crowdin/zh-CN.yml b/config/locales/crowdin/zh-CN.yml index 837f81e041e..35d766e4540 100644 --- a/config/locales/crowdin/zh-CN.yml +++ b/config/locales/crowdin/zh-CN.yml @@ -1481,6 +1481,7 @@ zh-CN: even: "必须是偶数。" exclusion: "是保留关键字。" feature_disabled: 不可用。 + feature_disabled_for_project: is disabled for this project. file_too_large: "太大 (最大大小为 %{count} 字节)。" filter_does_not_exist: "筛选器不存在。" format: "与预期的格式“%{expected}”不匹配。" diff --git a/config/locales/crowdin/zh-TW.yml b/config/locales/crowdin/zh-TW.yml index fe127651c77..d999654ef89 100644 --- a/config/locales/crowdin/zh-TW.yml +++ b/config/locales/crowdin/zh-TW.yml @@ -1481,6 +1481,7 @@ zh-TW: even: "必須是偶數" exclusion: "已保留" feature_disabled: 不可用。 + feature_disabled_for_project: is disabled for this project. file_too_large: "太大 (最大為 %{count} Bytes)." filter_does_not_exist: "過濾條件不存在" format: "與預期的格式“%{expected}”不符" @@ -2380,7 +2381,7 @@ zh-TW: baseline_comparison: 比較差異 board_view: 進階看板 calculated_values: 計算值 - capture_external_links: Capture External Links + capture_external_links: 擷取外部連結 conditional_highlighting: 條件強調 internal_comments: 內部備註 custom_actions: 自訂動作 @@ -2438,7 +2439,7 @@ zh-TW: conditional_highlighting: description: "想讓特定工作套件從眾多套件中脫穎而出?在工作套件列表中使用條件式醒目標示功能。" capture_external_links: - description: "Prevent social engineering attacks by capturing and warning about external links before users visit them." + description: "透過在使用者造訪外部連結前擷取外部連結並發出警告,防止社交工程攻擊。" work_package_query_relation_columns: description: "需要在工作套件清單中查看關聯或子套件嗎?" edit_attribute_groups: @@ -3239,7 +3240,7 @@ zh-TW: label_journal_diff: "內容對比" label_language: "語言" label_languages: "語言" - label_external_links: "External links" + label_external_links: "外部連結" label_locale: "語言和地區" label_jump_to_a_project: "前往一個專案..." label_keyword_plural: "關鍵字" @@ -4229,9 +4230,9 @@ zh-TW: setting_allowed_link_protocols: "允許連結" setting_allowed_link_protocols_text_html: >- 允許在工作套件描述、長文字欄位與留言中,將這些協定顯示為連結。例如:%{tel_code} 或 %{element_code}。每行輸入一個協定。
協定 %{http_code}、%{https_code} 與 %{mailto_code} 一律允許使用。 - setting_capture_external_links: "Capture external links" + setting_capture_external_links: "擷取外部連結" setting_capture_external_links_text: > - When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + 啟用後,格式化文字中的所有外部連結都會在離開應用程式前透過警告頁重定向。這有助於保護使用者遠離潛在的惡意外部網站。 setting_after_first_login_redirect_url: "首次登入重新導向" setting_after_first_login_redirect_url_text_html: > 設定使用者首次登入後的重新導向路徑。如果為空,則會重定向到上線導覽的首頁。
範例:/my/page @@ -4545,7 +4546,7 @@ zh-TW: project_mandate: "項目任務" submission: description_template: > - **This work package was automatically created upon completion of the %{wizard_name} workflow.** A PDF artifact containing all submitted information has been generated and attached to this work package for reference and audit purposes. If you need to update or re-run the initiation steps, you can reopen the wizard at any time by using the link below: + 此工作套件是在完成 %{wizard_name} 工作流程後自動建立的。 系統已產生一份包含所有已提交資訊的 PDF 成品,並已附加至此工作套件,供日後查閱與稽核使用。 若您需要更新內容或重新執行啟動步驟,可隨時透過下方連結重新開啟精靈: description: "當使用者提交專案啟動請求時,將會建立一個新的工作套件,並將請求成品以 PDF 檔案的形式附加到其中。下方的設定將定義這個新工作套件的類型、狀態和指派對象。" work_package_type: "工作套件類型" work_package_type_caption: "應用於儲存已完成成品之工作套件的類型。" @@ -5209,7 +5210,7 @@ zh-TW: hashed_token: display_value_placeholder: "***" external_link_warning: - title: "Leaving OpenProject" - warning_message: "You are about to leave OpenProject and visit an external website. Please be aware that external websites are not under our control and may have different privacy and security policies." - continue_message: "Are you sure you want to proceed to the following external link?" - continue_button: "Continue to external website" + title: "離開 OpenProject" + warning_message: "您即將離開 OpenProject 並訪問外部網站。請注意,外部網站不受我們控制,可能有不同的隱私和安全政策。" + continue_message: "您確定要前往下列外部連結嗎?" + continue_button: "繼續到外部網站" diff --git a/modules/auth_saml/config/locales/crowdin/id.yml b/modules/auth_saml/config/locales/crowdin/id.yml index 6079d3ecea4..77575588ecb 100644 --- a/modules/auth_saml/config/locales/crowdin/id.yml +++ b/modules/auth_saml/config/locales/crowdin/id.yml @@ -56,7 +56,7 @@ id: label_mapping: Pemetaan label_requested_attribute_for: "Atribut yang diminta untuk: %{attribute}" no_results_table: Belum ada penyedia identitas SAML yang didefinisikan. - notice_created: A new SAML identity provider was successfully created. + notice_created: Penyedia identitas SAML baru telah berhasil dibuat. plural: Penyedia identitas SAML singular: Penyedia identitas SAML requested_attributes: Atribut yang diminta diff --git a/modules/avatars/config/locales/crowdin/id.yml b/modules/avatars/config/locales/crowdin/id.yml index 0bc8049b989..f2317fa1a1d 100644 --- a/modules/avatars/config/locales/crowdin/id.yml +++ b/modules/avatars/config/locales/crowdin/id.yml @@ -3,7 +3,7 @@ id: plugin_openproject_avatars: name: "Avatar" description: >- - This plugin allows OpenProject users to upload a picture to be used as an avatar or use registered images from Gravatar. + Plugin ini memungkinkan pengguna OpenProject untuk mengunggah gambar yang akan digunakan sebagai avatar atau menggunakan gambar yang terdaftar di Gravatar. label_avatar: "Avatar" label_avatar_plural: "Avatar" label_current_avatar: "Avatar saat ini" diff --git a/modules/backlogs/config/locales/crowdin/id.yml b/modules/backlogs/config/locales/crowdin/id.yml index 9af42b71f3e..53073bec9da 100644 --- a/modules/backlogs/config/locales/crowdin/id.yml +++ b/modules/backlogs/config/locales/crowdin/id.yml @@ -21,8 +21,8 @@ #++ id: plugin_openproject_backlogs: - name: "OpenProject Backlogs" - description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." + name: "Backlog OpenProject" + description: "Modul ini menambahkan fitur yang memungkinkan tim yang gesit bekerja dengan OpenProject dalam proyek Scrum." activerecord: attributes: work_package: diff --git a/modules/bim/config/locales/crowdin/id.seeders.yml b/modules/bim/config/locales/crowdin/id.seeders.yml index 6485d390db1..b605618493c 100644 --- a/modules/bim/config/locales/crowdin/id.seeders.yml +++ b/modules/bim/config/locales/crowdin/id.seeders.yml @@ -433,24 +433,24 @@ id: options: name: Getting started text: | - We are glad you joined! We suggest to try a few things to get started in OpenProject. + Kami senang Anda telah bergabung! Kami sarankan untuk mencoba beberapa hal untuk memulai di OpenProject. - This demo project offers roles, workflows and work packages that are specialized for BIM. + Proyek demo ini menawarkan peran, alur kerja, dan paket kerja yang dikhususkan untuk BIM. - _Try the following steps:_ + _Cobalah langkah-langkah berikut ini:_ - 1. _Invite new members to your project:_ → Go to [Members]({{opSetting:base_url}}/projects/demo-bim-project/members) in the project navigation. - 2. _Upload and view 3D-models in IFC format:_ → Go to [BCF]({{opSetting:base_url}}/projects/demo-bim-project/bcf) in the project navigation. - 3. _Create and manage BCF issues linked directly in the IFC model:_ → Go to [BCF]({{opSetting:base_url}}/projects/demo-bim-project/bcf) → Create. - 4. _View the work in your projects:_ → Go to [Work packages]({{opSetting:base_url}}/projects/demo-bim-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22bcfIssueAssociated%22%2C%22o%22%3A%22%3D%22%2C%22v%22%3A%5B%22f%22%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D) in the project navigation. - 5. _Create a new work package:_ → Go to [Work packages → Create]({{opSetting:base_url}}/projects/demo-bim-project/work_packages/new?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22bcfIssueAssociated%22%2C%22o%22%3A%22%3D%22%2C%22v%22%3A%5B%22f%22%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D&type=11). - 6. _Create and update a Gantt chart:_ → Go to [Gantt chart]({{opSetting:base_url}}/projects/demo-bim-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22assignee%22%2C%22responsible%22%5D%2C%22tv%22%3Atrue%2C%22tzl%22%3A%22weeks%22%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D) in the project navigation. - 7. _Activate further modules:_ → Go to [Project settings → Modules]({{opSetting:base_url}}/projects/demo-bim-project/settings/modules). - 8. _Check out the tile view to get an overview of your BCF issues:_ → Go to [Work packages]({{opSetting:base_url}}/projects/demo-bim-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22id%3Aasc%22%2C%22f%22%3A%5B%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22card%22%7D) - 9. _Working agile? Create a new board:_ → Go to [Boards]({{opSetting:base_url}}/projects/demo-bim-project/boards) + 1. Undang anggota baru ke proyek Anda:_ → Buka [Anggota] ({{opSetting:base_url}}/projects/demo-bim-project/members) di navigasi proyek. + 2. Unggah dan lihat model 3D dalam format IFC:_ → Buka [BCF]({{opSetting:base_url}}/projects/demo-bim-project/bcf) di navigasi proyek. + 3. Buat dan kelola masalah BCF yang ditautkan secara langsung pada model IFC:_ → Buka [BCF]({{opSetting:base_url}}/projects/demo-bim-project/bcf) → Create. + 4. Lihat pekerjaan di proyek Anda:_ → Buka [Paket Kerja]({{opSetting:base_url}}/projects/demo-bim-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22bcfIssueAssociated%22%2C%22o%22%3A%22%3D%22%2C%22v%22%3A%5B%22f%22%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D) di navigasi proyek. + 5. Buat paket kerja baru:_ → Buka [Paket kerja → Buat]({{opSetting:base_url}}/projects/demo-bim-project/work_packages/new?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22bcfIssueAssociated%22%2C%22o%22%3A%22%3D%22%2C%22v%22%3A%5B%22f%22%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D&type=11). + 6. Buat dan perbarui diagram Gantt:_ → Buka [Diagram Gantt]({{opSetting:base_url}}/projects/demo-bim-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22assignee%22%2C%22responsible%22%5D%2C%22tv%22%3Atrue%2C%22tzl%22%3A%22weeks%22%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D) di navigasi proyek. + 7. Aktifkan modul-modul lebih lanjut:_ → Buka [Pengaturan proyek → Modul] ({{opSetting:base_url}}/projects/demo-bim-project/settings/modules). + 8. Lihat tampilan tile untuk mendapatkan gambaran umum tentang masalah BCF Anda:_ → Buka [Work packages]({{opSetting:base_url}}/projects/demo-bim-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22id%3Aasc%22%2C%22f%22%3A%5B%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22card%22%7D) + 9. Bekerja dengan cerdas? Buatlah papan baru:_ → Buka [Papan]({{opSetting:base_url}}/projects/demo-bim-project/boards) - Here you will find our [User Guides](https://www.openproject.org/docs/user-guide/). - Please let us know if you have any questions or need support. Contact us: [support\[at\]openproject.com](mailto:support@openproject.com). + Di sini Anda akan menemukan [Panduan Pengguna](https://www.openproject.org/docs/user-guide/). + Harap beri tahu kami jika Anda memiliki pertanyaan atau membutuhkan bantuan. Hubungi kami: [support\[at\]openproject.com](mailto:support@openproject.com). item_4: options: name: Member diff --git a/modules/bim/config/locales/crowdin/id.yml b/modules/bim/config/locales/crowdin/id.yml index 52cde1969c6..a1c5639bb9e 100644 --- a/modules/bim/config/locales/crowdin/id.yml +++ b/modules/bim/config/locales/crowdin/id.yml @@ -1,8 +1,8 @@ #English strings go here for Rails i18n id: plugin_openproject_bim: - name: "OpenProject BIM and BCF functionality" - description: "This OpenProject plugin introduces BIM and BCF functionality." + name: "Fungsi OpenProject BIM dan BCF" + description: "Plugin OpenProject ini memperkenalkan fungsi BIM dan BCF." bim: label_bim: 'BIM' bcf: diff --git a/modules/boards/config/locales/crowdin/id.yml b/modules/boards/config/locales/crowdin/id.yml index be5230b406e..7158a750184 100644 --- a/modules/boards/config/locales/crowdin/id.yml +++ b/modules/boards/config/locales/crowdin/id.yml @@ -1,8 +1,8 @@ #English strings go here id: plugin_openproject_boards: - name: "OpenProject Boards" - description: "Provides board views." + name: "Papan OpenProject" + description: "Menampilkan tampilan papan." permission_show_board_views: "Lihat papan" permission_manage_board_views: "Atur papan" project_module_board_view: "Papan" diff --git a/modules/budgets/config/locales/crowdin/id.yml b/modules/budgets/config/locales/crowdin/id.yml index 3d938dc8bb5..defcab75d34 100644 --- a/modules/budgets/config/locales/crowdin/id.yml +++ b/modules/budgets/config/locales/crowdin/id.yml @@ -75,7 +75,7 @@ id: label_yes: "Yes" label_budget_totals: "Total" label_budget_details: "Rincian anggaran" - notice_budget_conflict: "Work packages must be of the same project." + notice_budget_conflict: "Paket kerja harus berasal dari proyek yang sama." notice_no_budgets_available: "Pilihan budget tidak tersedia." permission_edit_budgets: "Edit budget" permission_view_budgets: "Tampilkan seluruh budget" diff --git a/modules/calendar/config/locales/crowdin/id.yml b/modules/calendar/config/locales/crowdin/id.yml index a4c9f56cfae..04c919316ba 100644 --- a/modules/calendar/config/locales/crowdin/id.yml +++ b/modules/calendar/config/locales/crowdin/id.yml @@ -1,8 +1,8 @@ #English strings go here id: plugin_openproject_calendar: - name: "OpenProject Calendar" - description: "Provides calendar views." + name: "Kalender OpenProject" + description: "Menampilkan tampilan kalender." label_calendar: "Kalender" label_calendar_plural: "Kalender" label_new_calendar: "Kalender baru" diff --git a/modules/costs/config/locales/crowdin/id.yml b/modules/costs/config/locales/crowdin/id.yml index 57db9c6295a..e9fe78c81c2 100644 --- a/modules/costs/config/locales/crowdin/id.yml +++ b/modules/costs/config/locales/crowdin/id.yml @@ -22,7 +22,7 @@ id: plugin_costs: name: "Waktu dan biaya" - description: "This module adds features for planning and tracking costs of projects." + description: "Modul ini menambahkan fitur untuk perencanaan dan pelacakan biaya proyek." activerecord: attributes: cost_entry: @@ -204,8 +204,8 @@ id: setting_costs_currency_format: "Format mata uang" setting_enforce_tracking_start_and_end_times: "Butuh waktu mulai dan selesai" setting_enforce_without_allow: "Membutuhkan waktu mulai dan selesai tidak mungkin dilakukan tanpa mengizinkannya" - setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." - setting_enforce_tracking_start_and_end_times_caption: "Makes entering start and finish times mandatory when logging time." + setting_allow_tracking_start_and_end_times_caption: "Memungkinkan untuk memasukkan waktu mulai dan selesai saat mencatat waktu." + setting_enforce_tracking_start_and_end_times_caption: "Membuat pengisian waktu mulai dan selesai menjadi wajib saat mencatat waktu." text_assign_time_and_cost_entries_to_project: "Masukkan laporan per-jam dan laporan biaya ke proyek" text_destroy_cost_entries_question: "%{cost_entries} digunakan pada work package yang akan dihapus. Keputusan anda?" text_destroy_time_and_cost_entries: "Hapus jumlah jam dan biaya yang terlapor" diff --git a/modules/documents/config/locales/crowdin/id.yml b/modules/documents/config/locales/crowdin/id.yml index 7f66ac9b500..2f317b0b2fd 100644 --- a/modules/documents/config/locales/crowdin/id.yml +++ b/modules/documents/config/locales/crowdin/id.yml @@ -21,8 +21,8 @@ #++ id: plugin_openproject_documents: - name: "OpenProject Documents" - description: "An OpenProject plugin to allow creation of documents in projects." + name: "Dokumen OpenProject" + description: "Plugin OpenProject untuk memungkinkan pembuatan dokumen dalam proyek." activerecord: errors: models: @@ -84,10 +84,10 @@ id: all: "Semua dokumen" types: "Tipe" collaboration_settings: "Kolaborasi secara langsung" - last_updated_at: "Last saved %{time}." + last_updated_at: "Terakhir disimpan %{time}." active_editors: "Editor yang aktif" active_editors_count: - other: "%{count} active editors" + other: "%{count} editor aktif" label_attachment_author: "Lampiran penulis" label_categories: "Kategori" new_category: "Kategori Baru" @@ -133,13 +133,13 @@ id: primary_action: Aktifkan kolaborasi secara langsung success: Kolaborasi secara langsung telah diaktifkan. disable_text_collaboration_dialog: - title: Disable real-time collaboration - heading: Disable real-time collaboration? + title: Nonaktifkan kolaborasi secara langsung + heading: Nonaktifkan kolaborasi secara langsung? confirmation_message: |- - All existing documents may become inaccessible. Please only do this if you are certain you want to disable - real-time collaboration and the BlockNote editor in this instance. - confirmation_checkbox_message: I understand that I might permanently lose data - success: Real-time collaboration has been disabled. + Semua dokumen yang ada akan mungkin tidak dapat diakses. Harap lakukan ini jika Anda yakin ingin menonaktifkan + kolaborasi secara langsung dan editor BlockNote dalam situasi ini. + confirmation_checkbox_message: Saya mengerti bahwa saya mungkin akan kehilangan data secara permanen + success: Kolaborasi secara langsung telah dinonaktifkan. label_document_added: "Dokumen ditambahkan" label_document_new: "Dokumen baru" label_document_plural: "Dokumen" @@ -147,7 +147,7 @@ id: label_document_title: "Judul" label_document_description: "Deskripsi" label_document_category: "Kategori" - label_document_type: "Type" + label_document_type: "Jenis" permission_manage_documents: "Kelola dokumen" permission_view_documents: "Lihat dokumen" project_module_documents: "Dokumen" diff --git a/modules/gantt/config/locales/crowdin/id.yml b/modules/gantt/config/locales/crowdin/id.yml index aa09edaf4b5..8d68b6e7b63 100644 --- a/modules/gantt/config/locales/crowdin/id.yml +++ b/modules/gantt/config/locales/crowdin/id.yml @@ -1,3 +1,3 @@ #English strings go here id: - project_module_gantt: "Gantt charts" + project_module_gantt: "Diagram Gantt" diff --git a/modules/github_integration/config/locales/crowdin/id.yml b/modules/github_integration/config/locales/crowdin/id.yml index 5b13a9d6396..bbe752b2f17 100644 --- a/modules/github_integration/config/locales/crowdin/id.yml +++ b/modules/github_integration/config/locales/crowdin/id.yml @@ -43,8 +43,8 @@ id: notice_deploy_target_created: Sebaran target telah dibuat notice_deploy_target_destroyed: Sebaran target telah dihapus plugin_openproject_github_integration: - name: "OpenProject GitHub Integration" - description: "Integrates OpenProject and GitHub for a better workflow" + name: "Integrasi OpenProject dengan GitHub" + description: "Mengintegrasikan OpenProject dan GitHub untuk alur kerja yang lebih baik" project_module_github: "GitHub" permission_show_github_content: "Tampilkan konten GitHub" permission_introspection: Baca versi inti OpenProject yang sedang berjalan dan bangun SHA @@ -52,4 +52,4 @@ id: Sampai saat ini, kami hanya mendukung OpenProject saja. text_deploy_target_api_key_info: > Kunci API OpenProject [API key](docs_url) yang dimiliki oleh pengguna yang memiliki izin introspeksi global. - text_pull_request_deployed_to: "%{pr_link} deployed to %{deploy_target_link}" + text_pull_request_deployed_to: "%{pr_link} disebarkan ke %{deploy_target_link}" diff --git a/modules/github_integration/config/locales/crowdin/js-id.yml b/modules/github_integration/config/locales/crowdin/js-id.yml index 857e59bf342..483308359f3 100644 --- a/modules/github_integration/config/locales/crowdin/js-id.yml +++ b/modules/github_integration/config/locales/crowdin/js-id.yml @@ -25,19 +25,19 @@ id: work_packages: tab_name: "GitHub" tab_header: - title: "Pull requests" + title: "Permintaan pull" copy_menu: - label: Git snippets - description: Copy git snippets to clipboard + label: Cuplikan Git + description: Salin cuplikan git ke papan klip git_actions: - branch_name: Branch name - commit_message: Commit message - cmd: Create branch with empty commit - title: Quick snippets for Git - copy_success: '✅ Copied!' - copy_error: '❌ Copy failed!' + branch_name: Nama cabang + commit_message: Pesan commit + cmd: Buat cabang dengan komit kosong + title: Cuplikan cepat untuk Git + copy_success: '✅ Tersalin!' + copy_error: '❌ Gagal tersalin!' tab_prs: - empty: 'There are no pull requests linked yet. Link an existing PR by using the code OP#%{wp_id} in the PR description or create a new PR.' + empty: 'Belum ada pull request yang terhubung. Hubungkan pull request yang sudah ada dengan menggunakan kode OP#%{wp_id} dalam deskripsi pull request atau buat pull request baru.' github_actions: Tindakan pull_requests: message: "Permintaan tarik #%{pr_number} %{pr_link} untuk %{repository_link} yang ditulis oleh %{github_user_link} telah %{pr_state}." diff --git a/modules/job_status/config/locales/crowdin/id.yml b/modules/job_status/config/locales/crowdin/id.yml index 17e172300e8..465d9d7379b 100644 --- a/modules/job_status/config/locales/crowdin/id.yml +++ b/modules/job_status/config/locales/crowdin/id.yml @@ -1,8 +1,8 @@ id: label_job_status_plural: "Status pekerjaan" plugin_openproject_job_status: - name: "OpenProject Job status" - description: "Listing and status of background jobs." + name: "Status Pekerjaan OpenProject" + description: "Daftar dan status pekerjaan latar belakang." job_status_dialog: download_starts: 'Unduhan dimulai secara otomatis.' link_to_download: 'Atau %{link} untuk mengunduh.' diff --git a/modules/ldap_groups/config/locales/crowdin/id.yml b/modules/ldap_groups/config/locales/crowdin/id.yml index 499cdae12b4..c7bcce5c765 100644 --- a/modules/ldap_groups/config/locales/crowdin/id.yml +++ b/modules/ldap_groups/config/locales/crowdin/id.yml @@ -5,8 +5,8 @@ id: title: 'Sinkronisasi grup LDAP' description: 'Sinkronisasi grup LDAP dengan grup OpenProject untuk mengatur pengguna, mengubah hak akses, dan memberikan fasilitas manajemen antar grup.' plugin_openproject_ldap_groups: - name: "OpenProject LDAP groups" - description: "Synchronization of LDAP group memberships." + name: "Grup LDAP OpenProject" + description: "Sinkronisasi keanggotaan grup LDAP." activerecord: attributes: ldap_groups/synchronized_group: diff --git a/modules/meeting/config/locales/crowdin/id.yml b/modules/meeting/config/locales/crowdin/id.yml index 0f73aaf8327..e5de7e95623 100644 --- a/modules/meeting/config/locales/crowdin/id.yml +++ b/modules/meeting/config/locales/crowdin/id.yml @@ -22,9 +22,9 @@ #English strings go here for Rails i18n id: plugin_openproject_meeting: - name: "OpenProject Meeting" + name: "Rapat OpenProject" description: >- - This module adds functions to support project meetings to OpenProject. Meetings can be scheduled selecting invitees from the same project to take part in the meeting. An agenda can be created and sent to the invitees. After the meeting, attendees can be selected and minutes can be created based on the agenda. Finally, the minutes can be sent to all attendees and invitees. + Modul ini menambahkan fungsi untuk mendukung rapat proyek di OpenProject. Rapat dapat dijadwalkan dengan memilih peserta undangan dari proyek yang sama untuk ikut serta dalam rapat. Agenda dapat dibuat dan dikirimkan kepada peserta undangan. Setelah rapat, peserta dapat dipilih dan notulen dapat dibuat berdasarkan agenda. Akhirnya, notulen dapat dikirimkan kepada semua peserta dan peserta undangan. activerecord: attributes: meeting: @@ -105,7 +105,7 @@ id: meeting_agenda_opened: Agenda rapat dibuka meeting_minutes: Laporan rapat diedit meeting_minutes_created: Laporan rapat dibuat - error_notification: "Failed to send notification." + error_notification: "Gagal mengirim notifikasi." error_notification_with_errors: "Gagal mengirim notifikasi. Berikut penerima yang tidak terkirim: %{recipients}" label_meeting: "Rapat" label_meeting_plural: "Rapat" @@ -142,9 +142,9 @@ id: label_recurring_meeting_next_occurrence: "Acara berikutnya" label_recurring_meeting_no_end_date: "Ada lebih banyak rapat yang dijadwalkan (%{schedule})." label_recurring_meeting_more: - other: "There are %{count} more scheduled meetings (%{schedule})." + other: "Terdapat %{count} rapat terjadwal lagi (%{schedule})." label_recurring_meeting_more_past: - other: "There are %{count} more past meetings." + other: "Terdapat %{count} rapat sebelumnya lagi." label_recurring_meeting_show_more: "Tampilkan lebih" label_recurring_meeting_series_create: "Buat seri rapat" label_recurring_meeting_series_edit: "Sunting seri rapat" @@ -206,67 +206,67 @@ id: participants: "Salin daftar peserta" to_clipboard: "Salin tautan ke papan klip" email: - send_emails: "Email participants" + send_emails: "Email peserta" send_invitation_emails: > Kirim undangan melalui email segera kepada peserta yang telah dipilih di atas. Anda juga dapat melakukannya secara manual kapan saja nanti. send_invitation_emails_structured: "Kirim undangan melalui email segera kepada semua peserta. Anda juga dapat melakukannya secara manual kapan saja nanti." open_meeting_link: "Rapat terbuka" - open_my_meetings_link: "Go to My meetings" + open_my_meetings_link: "Buka Rapat Saya" series: - title: "[%{project_name}] Meeting series '%{title}'" - summary: "%{actor} has invited you to a new meeting series '%{title}'" + title: "[%{project_name}] Seri rapat '%{title}'" + summary: "%{actor} telah mengundang Anda ke seri rapat baru berjudul ‘%{title}’" series_updated: - title: "[%{project_name}] Meeting series '%{title}' has been updated" - summary: "Meeting series '%{title}' has been updated by %{actor}" - old_schedule: "Old schedule" - new_schedule: "New schedule" + title: "[%{project_name}] Seri rapat ‘%{title}’ telah diperbarui" + summary: "Seri rapat '%{title}' telah diperbarui oleh %{actor}" + old_schedule: "Jadwal sebelumnya" + new_schedule: "Jadwal baru" invited: - summary: "%{actor} has sent you an invitation for the meeting '%{title}'" + summary: "%{actor} telah mengirim undangan kepada Anda untuk rapat '%{title}'" cancelled: - header: "Cancelled: Meeting '%{title}'" - header_occurrence: "Cancelled: Meeting occurrence '%{title}'" - header_series: "Cancelled: Meeting series '%{title}'" - summary_occurrence: "An occurrence of '%{title}' has been cancelled by %{actor}." - summary_series: "Meeting series '%{title}' has been cancelled by %{actor}." - summary: "'%{title}' has been cancelled by %{actor}." - date_time: "Scheduled date/time" + header: "Dibatalkan: Rapat '%{title}'" + header_occurrence: "Dibatalkan: Rapat terjadwal '%{title}'" + header_series: "Dibatalkan: Seri rapat '%{title}'" + summary_occurrence: "Acara ‘%{title}’ telah dibatalkan oleh %{actor}." + summary_series: "Seri rapat '%{title}' telah dibatalkan oleh %{actor}." + summary: "'%{title}' telah dibatalkan oleh %{actor}." + date_time: "Tanggal/waktu yang dijadwalkan" participant_added: - header: "Meeting '%{title}' - Participant added" - header_series: "Meeting series '%{title}' - Participant added" - summary: "%{actor} added %{participant} to the meeting '%{title}'" - summary_series: "%{actor} added %{participant} to the meeting series '%{title}'" + header: "Rapat '%{title}' - Peserta telah ditambahkan" + header_series: "Seri rapat '%{title}' - Peserta telah ditambahkan" + summary: "%{actor} menambahkan %{participant} ke rapat '%{title}'" + summary_series: "%{actor} menambahkan %{participant} ke seri rapat '%{title}'" participant_removed: - header: "Meeting '%{title}' - Participant removed" - header_series: "Meeting series '%{title}' - Participant removed" - summary: "%{actor} removed %{participant} from the meeting '%{title}'" - summary_series: "%{actor} removed %{participant} from the meeting series '%{title}'" + header: "Rapat '%{title}' - Peserta telah dihapus" + header_series: "Seri rapat '%{title}' - Peserta telah dihapus" + summary: "%{actor} menghapus %{participant} ke rapat '%{title}'" + summary_series: "%{actor} menghapus %{participant} ke seri rapat '%{title}'" ended: - header_series: "Ended: Meeting series '%{title}'" + header_series: "Selesai: Seri rapat '%{title}'" summary_series: "Meeting series '%{title}' has been ended by %{actor}." updated: - header: "Meeting '%{title}' has been updated" - summary: "Meeting '%{title}' has been updated by %{actor}" - body: "The meeting '%{title}' has been updated by %{actor}." + header: "Rapat '%{title}' telah diperbarui" + summary: "Seri rapat '%{title}' telah diperbarui oleh %{actor}" + body: "Tapat '%{title}' telah diperbarui oleh %{actor}." old_title: "Old title" new_title: "New title" old_date_time: "Tanggal/waktu lama" new_date_time: "Tanggal/waktu baru" - old_location: "Old location" - new_location: "New location" - label_mail_all_participants: "Send email invite to participants" + old_location: "Lokasi sebelumnya" + new_location: "Lokasi baru" + label_mail_all_participants: "Kirim undangan melalui email kepada peserta" types: - one_time: "One-time" - recurring: "Recurring" - recurring_text: "Create meeting series with dynamic template for each occurrence." - structured_text: "Organize your meeting as a dynamic list of agenda items, optionally linking them to a work package." - structured_text_copy: "Copying a meeting will currently not copy the associated meeting agenda items, just the details" + one_time: "Satu kali" + recurring: "Berulang" + recurring_text: "Buat seri rapat dengan templat dinamis untuk setiap acara." + structured_text: "Atur rapat Anda sebagai daftar agenda yang dinamis dengan opsi untuk menghubungkannya ke paket kerja." + structured_text_copy: "Saat ini, menyalin rapat tidak akan menyalin item agenda rapat yang terkait, hanya detailnya saja" copied: "Disalin dari Meeting #%{id}" delete_dialog: one_time: - title: "Delete meeting" - heading: "Delete this meeting?" + title: "Hapus rapat" + heading: "Hapus rapat ini?" confirmation_message_html: > - This action is not reversible. Please proceed with caution. + Tindakan ini tidak dapat dibatalkan. Harap berhati-hati saat melakukannya. occurrence: title: "Cancel meeting occurrence" heading: "Cancel this meeting occurrence?" @@ -274,88 +274,88 @@ id: Any meeting information not in the template will be lost. Do you want to continue? confirm_button: "Cancel occurrence" blankslate: - title: "There are no meetings to display" - desc: "You can create a new meeting or change filter criteria" - label_export_pdf: "Export PDF" + title: "Tidak ada rapat yang ditampilkan" + desc: "Anda dapat membuat pertemuan baru atau mengubah kriteria filter" + label_export_pdf: "Ekspor PDF" export: - your_meeting_export: "Meeting is being exported" + your_meeting_export: "Rapat sedang diekspor" minutes: - footer_page_numbers: "P. %{current_page} of %{total_pages}" + footer_page_numbers: "H. %{current_page} dari %{total_pages}" author: "Penulis" date: "Tanggal" time: "Waktu" location: "Lokasi" title: "Laporan" export_pdf_dialog: - title: Export PDF + title: Ekspor PDF description: Generate a printable PDF file of this meeting at the current state. templates: default: label: Default - caption: The default template is suitable for most meetings and represent the current state. + caption: Templat default cocok untuk sebagian besar pertemuan dan mewakili keadaan saat ini. minutes: label: Laporan - caption: The minutes template is suitable for closed and archived meetings. + caption: Templat notulen ini cocok untuk rapat tertutup dan yang telah diarsipkan. first_page_header_left: - label: First page header left - caption: This text will appear on the first page at the left of the header. + label: Tajuk kiri halaman pertama + caption: Teks ini akan muncul di halaman pertama sebelah kiri tajuk. author: label: Penulis - caption: The author of the minutes will be displayed in the subtitle. + caption: Penulis notulen akan ditampilkan di subjudul. include_participants: - label: Include list of participants + label: Sertakan daftar peserta caption: A list of participants will be preset above the meeting agenda. include_attachments: - label: Include list of attachments + label: Sertakan daftar lampiran caption: A list containing the filenames of attachments will be appended at the end. include_backlog: - label: Include backlog + label: Sertakan backlog caption: Backlog elements are not normally considered part of a meeting occurrence but you can choose to include them. include_outcomes: - label: Include agenda outcomes + label: Sertakan hasil dari agenda caption: If your agenda outcomes contain confidential information, you can choose to not include them in the export. footer_text: label: Teks footer - caption: This text will appear on every page at the center of the footer. + caption: Teks ini akan muncul di setiap halaman di tengah footer. submit_button: Download notifications: sidepanel: - title: "Email calendar updates" + title: "Pembaruan kalender email" description: - disabled: "Participants will not receive an email informing them of changes." - enabled: "All participants will receive updated calendar invites via email informing them of changes." - change_via_template: "To change this, edit the series template." + disabled: "Peserta tidak akan menerima email yang memberitahukan mereka tentang perubahan." + enabled: "Seluruh peserta akan menerima undangan kalender yang diperbarui melalui email yang memberitahukan mereka tentang perubahan." + change_via_template: "Untuk mengubahnya, sunting templat serinya." dialog: title: - enable: "Enable email calendar updates?" - disable: "Disable email calendar updates?" + enable: "Aktifkan pembaruan kalender melalui email?" + disable: "Nonaktifkan pembaruan kalender melalui email?" message: enable: > All participants will receive updated calendar invites via email every time there is a change to the meeting date, time, location or participants. Once enabled, an email will be sent out immediately to all participants. disable: > - Participants will no longer receive updated calendar invites via email when there are changes to the meeting date, time, location or participants. If they already had an invite for this meeting, it might no longer be accurate. + Peserta tidak lagi akan menerima undangan kalender yang diperbarui melalui email ketika ada perubahan pada tanggal, waktu, lokasi, atau peserta rapat. Jika mereka sudah menerima undangan untuk rapat ini, undangan tersebut mungkin tidak lagi akurat. confirm_label: - enable: "Enable email updates" - disable: "Disable email updates" + enable: "Aktifkan pembaruan email" + disable: "Nonaktifkan pembaruan email" banner: participants: enabled: > - All participants will receive updated calendar invites via email when you add or remove participants. + Seluruh peserta akan menerima undangan kalender yang diperbarui melalui email saat Anda menambahkan atau menghapus peserta. disabled: > - Email calendar updates are disabled. Participants will not receive an email informing them when you add or remove participants. + Pembaruan kalender melalui email telah dinonaktifkan. Peserta tidak akan menerima email pemberitahuan ketika Anda menambahkan atau menghapus peserta. onetime: enabled: > - All participants will receive updated calendar invites via email when you add or remove participants. + Seluruh peserta akan menerima undangan kalender yang diperbarui melalui email saat Anda menambahkan atau menghapus peserta. disabled: > - Participants will not receive an email informing them of changes to meeting date, time or participants. + Peserta tidak akan menerima email yang memberitahukan perubahan tanggal, waktu, atau peserta rapat. occurrence: enabled: > - Email calendar updates are enabled for the meeting series. All participants will receive updated calendar invites informing them of your changes to this occurrence. + Pembaruan kalender melalui email telah diaktifkan untuk rangkaian pertemuan ini. Semua peserta akan menerima undangan kalender yang diperbarui yang memberitahukan mereka tentang perubahan yang Anda lakukan pada pertemuan ini. disabled: > - Email calendar updates are disabled for the meeting series. Participants will not receive an email informing them of your changes to this occurrence. + Pembaruan kalender melalui email dinonaktifkan untuk seri pertemuan ini. Peserta tidak akan menerima email yang memberitahukan mereka tentang perubahan yang Anda lakukan pada pertemuan ini. template: enabled: > - Email calendar updates are enabled for the meeting series. All participants will receive updated calendar invites informing them of your changes to this template or to individual occurrences. + Pembaruan kalender melalui email telah diaktifkan untuk rangkaian pertemuan ini. Semua peserta akan menerima undangan kalender yang diperbarui yang memberitahukan mereka tentang perubahan yang Anda lakukan pada templat ini atau pada acara individu. disabled: > Pembaruan kalender melalui email dinonaktifkan untuk seri pertemuan ini. Peserta tidak akan menerima email yang memberitahukan mereka tentang perubahan yang Anda lakukan pada templat ini atau pada kejadian individu. presentation_mode: @@ -436,203 +436,203 @@ id: Rapat terbuka memiliki agenda yang dapat diedit dan muncul di bagian ‘Rapat Saya’ masing-masing pengguna. Perubahan pada templat seri rapat tidak memengaruhi rapat yang sudah dibuka. blankslate: title: "Tidak ada rapat terbuka untuk saat ini" - desc: "You can manually open a planned meeting by clicking on the 'Open' button below" + desc: "Anda dapat membuka rapat yang telah dijadwalkan secara manual dengan mengklik tombol ‘Buka’ di bawah ini" planned: title: "Terencana" subtitle: > - The following meetings are planned in the recurring meeting schedule but are not open yet. Every time a planned meeting starts, the next one will automatically be opened for you. You can also open planned meetings manually to import the template and start editing the agenda. + Pertemuan-pertemuan berikut ini telah dijadwalkan dalam jadwal pertemuan berulang, tetapi belum dibuka. Setiap kali pertemuan yang dijadwalkan dimulai, pertemuan berikutnya akan secara otomatis dibuka untuk Anda. Anda juga dapat membuka pertemuan yang dijadwalkan secara manual untuk mengimpor templat dan mulai mengedit agenda. blankslate: - title: "No more planned meetings" + title: "Tidak ada pertemuan yang direncanakan lagi" desc: > - There are no additional meetings planned in this series. To schedule additional meetings or extend the series, go to the template and edit meeting details to change the end date, frequency or interval. + Tidak ada pertemuan tambahan yang direncanakan dalam seri ini. Untuk menjadwalkan pertemuan tambahan atau memperpanjang seri, buka templat dan sunting detail pertemuan untuk mengubah tanggal akhir, frekuensi, atau interval. delete_dialog: - title: "Delete meeting series" - heading: "Permanently delete this meeting series?" + title: "Hapus seri rapat" + heading: "Hapus seri rapat ini secara permanen?" confirmation_message_html: zero: > - The meeting series %{title} does not have any meeting occurrences. The series will be deleted for everyone. Please proceed with caution. + Seri pertemuan %{title} tidak memiliki jadwal pertemuan. Seri ini akan dihapus untuk semua orang. Harap berhati-hati. one: > - Deleting %{title} will also delete one occurrence in this series. This action is not reversible. Please proceed with caution. + Menghapus %{title} juga akan menghapus satu entri dalam seri ini. Tindakan ini tidak dapat dibatalkan. Harap berhati-hati saat melakukannya. other: > - Deleting %{title} will delete all %{count} occurrences in this series. This action is not reversible. Please proceed with caution. + Menghapus %{title} akan menghapus %{count} entri dalam seri ini. Tindakan ini tidak dapat dibatalkan. Harap berhati-hati saat melakukannya. scheduled_delete_dialog: - title: "Cancel meeting occurrence" - heading: "Cancel this meeting occurrence?" + title: "Batalkan pelaksanaan rapat" + heading: "Batalkan pelaksanaan rapat ini?" confirmation_message_html: > - Any meeting information not in the template will be lost. Do you want to continue? - confirm_button: "Cancel occurrence" + Informasi rapat yang tidak tercantum dalam templat akan hilang. Apakah Anda ingin melanjutkan? + confirm_button: "Batalkan acara" end_series_dialog: - title: "End meeting series" + title: "Akhiri seri rapat" notice_successful_notification: "Email calendar update sent to all participants" notice_timezone_missing: Tidak ada zona waktu yang ditetapkan sehingga diasumsikan %{zone}. Untuk memilih zona waktu, silakan mengeklik di sini. - notice_meeting_updated: "This page has been updated by someone else. Reload to view changes." + notice_meeting_updated: "Halaman ini telah diperbarui oleh orang lain. Segarkan halaman untuk melihat perubahan." permission_create_meetings: "Membuat rapat" permission_edit_meetings: "Edit rapat" permission_delete_meetings: "Hapus rapat" permission_view_meetings: "Lihat rapat" permission_manage_agendas: "Kelola agenda" - permission_manage_agendas_explanation: "Allows creating, editing and removing agenda items" - permission_manage_outcomes: "Manage outcomes" - permission_send_meeting_invites_and_outcomes: "Send meeting invites and outcomes to participants" + permission_manage_agendas_explanation: "Memungkinkan untuk membuat, menyunting, dan menghapus item agenda" + permission_manage_outcomes: "Kelola hasil" + permission_send_meeting_invites_and_outcomes: "Kirim undangan rapat dan hasil rapat kepada peserta" project_module_meetings: "Rapat" text_duration_in_hours: "Durasi dalam jam" text_in_hours: "dalam jam" text_meeting_agenda_for_meeting: 'agenda untuk rapat "%{meeting}"' - text_meeting_series_end_early_heading: "Delete future occurrences?" - text_meeting_series_end_early: "Ending the series will delete any future open or scheduled meeting occurrences" - text_meeting_closing_are_you_sure: "Are you sure you want to close the meeting agenda?" + text_meeting_series_end_early_heading: "Hapus acara yang akan datang?" + text_meeting_series_end_early: "Mengakhiri seri ini akan menghapus semua rapat terbuka atau rapat yang akan datang" + text_meeting_closing_are_you_sure: "Apakah Anda yakin ingin menutup agenda rapat?" text_meeting_agenda_open_are_you_sure: "Ini akan mengganti semua perubahan dalam beberapa menit! Apakah anda ingin lanjut?" text_meeting_minutes_for_meeting: 'laporan untuk rapat "%{meeting}"' text_notificiation_invited: "Surel ini berisi entri ics untuk rapat di bawah:" text_meeting_ics_description: >- - Link to meeting: %{url} + Tautan ke rapat: %{url} text_meeting_ics_meeting_series_description: >- - Link to meeting series: %{url} + Tautan ke seri rapat: %{url} text_meeting_occurrence_ics_description: >- - Link to meeting occurrence: %{url} Link to meeting series: %{series_url} - text_meeting_empty_heading: "Your meeting is empty" - text_meeting_empty_description1: "Start by adding agenda items below. Each item can be as simple as just a title, but you can also add additional details like duration and notes." - text_meeting_empty_description2: 'You can also add references to existing work packages. When you do, related notes will automatically be visible in the work package''s "Meetings" tab.' - label_meeting_empty_action: "Add agenda item" - label_meeting_actions: "Meeting actions" - label_meeting_edit_title: "Edit meeting title" - label_meeting_delete: "Delete meeting" + Tautan untuk acara rapat: %{url} Tautan untuk seri rapat: %{series_url} + text_meeting_empty_heading: "Rapat anda kosong" + text_meeting_empty_description1: "Mulailah dengan menambahkan item agenda di bawah ini. Setiap item dapat sesederhana hanya judul, tetapi Anda juga dapat menambahkan detail tambahan seperti durasi dan catatan." + text_meeting_empty_description2: 'Anda juga dapat menambahkan referensi ke paket kerja yang sudah ada. Saat Anda melakukannya, catatan terkait akan secara otomatis terlihat di tab “Pertemuan” paket kerja tersebut.' + label_meeting_empty_action: "Tambahkan item agenda" + label_meeting_actions: "Tindakan rapat" + label_meeting_edit_title: "Sunting judul rapat" + label_meeting_delete: "Hapus rapat" label_meeting_created_by: "Created by" - label_meeting_last_updated: "Last updated" - label_meeting_reload: "Reload" - label_meeting_index_today: "Today" - label_meeting_index_tomorrow: "Tomorrow" - label_meeting_index_this_week: "Later this week" - label_meeting_index_later: "Next week and later" - label_agenda_items: "Agenda items" + label_meeting_last_updated: "Terakhir diperbarui" + label_meeting_reload: "Muat ulang" + label_meeting_index_today: "Hari Ini" + label_meeting_index_tomorrow: "Besok" + label_meeting_index_this_week: "Akhir pekan ini" + label_meeting_index_later: "Minggu depan dan selanjutnya" + label_agenda_items: "Item agenda" label_agenda_items_reordered: "disusun ulang" - label_agenda_item_add: "Add agenda item" - label_agenda_item_remove_from_agenda: "Remove from agenda" - label_agenda_item_remove_from_backlog: "Remove from backlog" - label_agenda_item_undisclosed_wp: "Work package #%{id} not visible" - label_agenda_item_deleted_wp: "Deleted work package reference" - label_agenda_item_actions: "Agenda items actions" - label_agenda_item_move_to_next_title: "Move to next meeting?" + label_agenda_item_add: "Tambahkan item agenda" + label_agenda_item_remove_from_agenda: "Hapus dari agenda" + label_agenda_item_remove_from_backlog: "Hapus dari backlog" + label_agenda_item_undisclosed_wp: "Paket kerja #%{id} tidak terlihat" + label_agenda_item_deleted_wp: "Referensi paket kerja yang terhapus" + label_agenda_item_actions: "Tindakan item agenda" + label_agenda_item_move_to_next_title: "Pindahkan ke rapat selanjutnya?" label_agenda_item_move: "Pindahkan" - label_agenda_item_move_to_next: "Move to next meeting" - label_agenda_item_move_to_backlog: "Move to backlog" - label_agenda_item_move_to_current_meeting: "Move to current meeting" - label_agenda_item_move_to_section: "Move to section" + label_agenda_item_move_to_next: "Pindahkan ke rapat selanjutnya" + label_agenda_item_move_to_backlog: "Pindahkan ke backlog" + label_agenda_item_move_to_current_meeting: "Pindahkan ke rapat saat ini" + label_agenda_item_move_to_section: "Pindahkan ke bagian" label_agenda_item_move_to_top: "Pindah paling atas" label_agenda_item_move_to_bottom: "Pindahkan paling bawah" label_agenda_item_move_up: "Naikkan" label_agenda_item_move_down: "Pindahkan ke bawah" - label_agenda_item_duplicate: "Duplicate" - label_agenda_item_duplicate_in_next: "Duplicate in next occurrence" - label_agenda_item_duplicate_in_next_title: "Duplicate in next occurrence?" + label_agenda_item_duplicate: "Duplikat" + label_agenda_item_duplicate_in_next: "Duplikat pada acara berikutnya" + label_agenda_item_duplicate_in_next_title: "Duplikat pada acara berikutnya?" label_agenda_item_add_notes: "Tambahkan catatan" - label_agenda_item_add_outcome: "Add outcome" - label_agenda_item_work_package_add: "Add work package" - label_agenda_item_work_package: "Agenda item work package" - label_section_rename: "Rename section" - label_agenda_outcome: "Outcome" - label_agenda_new_outcome: "New outcome" - label_agenda_outcome_actions: "Agenda outcome actions" - label_agenda_outcome_edit: "Edit outcome" - label_agenda_outcome_delete: "Remove outcome" - label_added_as_outcome: "Added as outcome" - label_write_outcome: "Write outcome" - label_existing_work_package: "Existing work package" - text_outcome_not_editable_anymore: "This outcome is not editable anymore." - text_outcome_cannot_be_added: "An outcome can no longer be added." - label_backlog_clear: "Clear backlog" - label_backlog_clear_button: "Clear all" - text_backlog_clear_error: "One or more errors occurred while trying to clear the backlog." - label_agenda_backlog: "Agenda backlog" + label_agenda_item_add_outcome: "Tambahkan hasil" + label_agenda_item_work_package_add: "Tambahkan paket kerja" + label_agenda_item_work_package: "Paket kerja item agenda" + label_section_rename: "Ganti nama bagian" + label_agenda_outcome: "Hasil" + label_agenda_new_outcome: "Hasil baru" + label_agenda_outcome_actions: "Tindakan hasil agenda" + label_agenda_outcome_edit: "Sunting hasil" + label_agenda_outcome_delete: "Hapus hasil" + label_added_as_outcome: "Ditambahkan sebagai hasil" + label_write_outcome: "Tulis hasil" + label_existing_work_package: "Paket kerja yang tersedia" + text_outcome_not_editable_anymore: "Hasil ini tidak dapat disunting lagi." + text_outcome_cannot_be_added: "Hasil tidak dapat ditambahkan lagi." + label_backlog_clear: "Hapus backlog" + label_backlog_clear_button: "Hapus semua" + text_backlog_clear_error: "Terjadi satu atau beberapa kesalahan saat mencoba menghapus backlog." + label_agenda_backlog: "Backlog agenda" text_agenda_backlog: > - This backlog is unique to this one-time meeting. You can drag items in and out to add or remove them from the meeting agenda. - label_agenda_backlog_clear_title: "Clear agenda backlog?" + Backlog ini khusus untuk pertemuan satu kali ini. Anda dapat menyeret item masuk dan keluar untuk menambahkan atau menghapusnya dari agenda pertemuan. + label_agenda_backlog_clear_title: "Hapus backlog agenda?" text_agenda_backlog_clear_description: > Are you sure you want to remove all items currently in the agenda backlog? This action is not reversible. - label_series_backlog: "Series backlog" + label_series_backlog: "Backlog seri" text_series_backlog: > - The backlog is shared with all occurrences of this series. You can drag items in and out to add or remove them from a particular meeting. - label_series_backlog_clear_title: "Clear series backlog?" + Backlog dibagikan dengan semua acara dalam seri ini. Anda dapat menyeret item masuk dan keluar untuk menambahkan atau menghapusnya dari pertemuan tertentu. + label_series_backlog_clear_title: "Hapus backlog seri?" text_series_backlog_clear_description: > - This will remove all items in the series backlog, which is shared with all meetings in the series. Are you sure you want to continue? This action is not reversible. - text_agenda_item_title: 'Agenda item "%{title}"' - text_agenda_work_package_deleted: "Agenda item for deleted work package" - text_deleted_agenda_item: "Deleted agenda item" + Ini akan menghapus semua item dalam backlog seri yang dibagikan dengan semua pertemuan dalam seri tersebut. Apakah Anda yakin ingin melanjutkan? Tindakan ini tidak dapat dibatalkan. + text_agenda_item_title: 'Item agenda "%{title}"' + text_agenda_work_package_deleted: "Agenda item untuk paket kerja yang dihapus" + text_deleted_agenda_item: "Item agenda yang terhapus" label_initial_meeting_details: "Rapat" - label_meeting_details: "Meeting details" - label_meeting_series_details: "Meeting series details" - label_meeting_details_edit: "Edit meeting details" - label_meeting_state: "Meeting status" - label_meeting_state_draft: "Draft" + label_meeting_details: "Rincian rapat" + label_meeting_series_details: "Rincian seri rapat" + label_meeting_details_edit: "Sunting rincian rapat" + label_meeting_state: "Status rapat" + label_meeting_state_draft: "Draf" label_meeting_state_open: "Buka" label_meeting_state_closed: "Closed" - label_meeting_state_agenda_created: "Agenda created" + label_meeting_state_agenda_created: "Agenda telah dibuat" label_meeting_state_planned: "Terencana" - label_meeting_state_cancelled: "Cancelled" - label_meeting_state_skipped: "Skipped" + label_meeting_state_cancelled: "Dibatalkan" + label_meeting_state_skipped: "Dilewati" label_meeting_state_in_progress: "In Progress" - label_meeting_reopen_action: "Reopen meeting" - label_meeting_close_action: "Close meeting" - label_meeting_in_progress_action: "Start meeting" - label_meeting_open_action: "Open meeting" - text_meeting_draft_description: "Prepare your agenda in draft mode. This meeting will not send out any calendar updates or invites, even if you change meeting details or add/remove participants." - text_meeting_open_description: "You can add/remove agenda items and participants. Once the agenda is ready, mark it as in progress to document outcomes." - text_meeting_closed_description: "This meeting is closed. You cannot add/remove agenda items anymore." - text_meeting_in_progress_description: "You can modify the agenda, document outcomes for each item and track attendance for participants. Once the meeting is complete, you can mark it as closed to lock it." - text_meeting_open_dropdown_description: "Any existing outcomes will remain but users will not be able to add new outcomes." + label_meeting_reopen_action: "Buka rapat kembali" + label_meeting_close_action: "Tutup rapat" + label_meeting_in_progress_action: "Mulai rapat" + label_meeting_open_action: "Buka rapat" + text_meeting_draft_description: "Siapkan agenda Anda dalam mode draf. Rapat ini tidak akan mengirimkan pembaruan kalender atau undangan bahkan jika Anda mengubah detail rapat atau menambahkan/menghapus peserta." + text_meeting_open_description: "Anda dapat menambahkan/menghapus item agenda dan peserta. Setelah agenda siap, tandai sebagai sedang berlangsung untuk mencatat hasilnya." + text_meeting_closed_description: "Rapat ini telah ditutup. Anda tidak dapat menambahkan atau menghapus item agenda lagi." + text_meeting_in_progress_description: "Anda dapat mengubah agenda, mencatat hasil untuk setiap item, dan melacak kehadiran peserta. Setelah rapat selesai, Anda dapat menandainya sebagai selesai untuk mengunci rapat tersebut." + text_meeting_open_dropdown_description: "Hasil yang sudah ada akan tetap ada, tetapi pengguna tidak akan dapat menambahkan hasil baru." text_meeting_in_progress_dropdown_description: "Document outcomes like information needs or decisions taken during the meeting." text_meeting_closed_dropdown_description: "This meeting is closed. You cannot modify agenda items or outcomes anymore." - text_meeting_draft_banner: "You are currently in draft mode. This meeting will not send out any calendar updates or invites, even if you change meeting details or add/remove participants." - text_exit_draft_mode_dialog_title: "Open this meeting and send invites?" - text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." - text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" - text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." - text_meeting_not_editable_anymore: "This meeting is not editable anymore." - text_meeting_not_present_anymore: "This meeting was deleted. Please select another meeting." - label_add_work_package_to_meeting_dialog_title: "Select meeting" + text_meeting_draft_banner: "Saat ini, Anda berada dalam mode draf. Rapat ini tidak akan mengirimkan pembaruan kalender atau undangan, bahkan jika Anda mengubah detail rapat atau menambahkan/menghapus peserta." + text_exit_draft_mode_dialog_title: "Buka rapat ini dan kirim undangan?" + text_exit_draft_mode_dialog_subtitle: "Anda tidak dapat kembali ke mode draf setellah Anda menjadwalkan rapat." + text_exit_draft_mode_dialog_template_title: "Buka acara yang pertama dalam seri rapat ini?" + text_exit_draft_mode_dialog_template_subtitle: "Anda tidak dapat kembali ke mode draf setelah ini." + text_meeting_not_editable_anymore: "Rapat ini tidak dapat disunting kembali." + text_meeting_not_present_anymore: "Rapat ini telah dihapus. Silakan pilih rapat yang lain." + label_add_work_package_to_meeting_dialog_title: "Pilih rapat" label_add_work_package_to_meeting_section_label: "Bagian" - label_add_work_package_to_meeting_dialog_button: "Add to meeting" - label_meeting_selection_caption: "It is only possible to add this work package to an upcoming or an ongoing meeting." - label_section_selection_caption: "Choose a particular section of the agenda or add it to the backlog." - placeholder_section_select_meeting_first: "Meeting selection is required first" - text_add_work_package_to_meeting_form: "The work package will be added to the selected meeting or backlog as an agenda item." - text_add_work_package_to_meeting_description: "A work package can be added to one or multiple meetings for discussion. Any notes concerning it are also visible here." - text_agenda_item_no_notes: "No notes provided" - text_agenda_item_not_editable_anymore: "This agenda item is not editable anymore." - text_agenda_item_move_next_meeting: "This item will be moved to the next meeting on %{date} at %{time}." - text_agenda_item_moved_to_next_meeting: "Agenda item moved to the next meeting, on %{date}" - text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." - text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" - text_work_package_has_no_upcoming_meeting_agenda_items: "This work package is not scheduled in an upcoming meeting agenda yet." + label_add_work_package_to_meeting_dialog_button: "Tambahkan ke rapat" + label_meeting_selection_caption: "Paket pekerjaan ini hanya dapat ditambahkan ke rapat yang akan datang atau yang sedang berlangsung." + label_section_selection_caption: "Pilih bagian tertentu dari agenda tersebut atau tambahkan ke backlog." + placeholder_section_select_meeting_first: "Diperlukan pemilihan rapat terlebih dahulu" + text_add_work_package_to_meeting_form: "Paket kerja akan ditambahkan ke rapat yang dipilih atau backlog sebagai item agenda." + text_add_work_package_to_meeting_description: "Paket kerja dapat ditambahkan ke satu atau beberapa rapat untuk dibahas. Catatan apa pun yang berkaitan dengannya juga dapat dilihat di sini." + text_agenda_item_no_notes: "Tidak ada catatan yang diberikan" + text_agenda_item_not_editable_anymore: "Item agenda ini tidak dapat disunting kembali." + text_agenda_item_move_next_meeting: "Item ini akan dipindahkan ke rapat selanjutnya pada %{date} pukul %{time}." + text_agenda_item_moved_to_next_meeting: "Item agenda ini dipindahkan ke rapat berikutnya, yaitu pada %{date}" + text_agenda_item_duplicate_in_next_meeting: "Apakah Anda yakin ingin menambahkan salinan item agenda ini ke rapat berikutnya pada %{date} pukul %{time}? Hasil rapat tidak akan diduplikasi." + text_agenda_item_duplicated_in_next_meeting: "Agenda item yang sama terulang dalam rapat berikutnya, yaitu pada %{date}" + text_work_package_has_no_upcoming_meeting_agenda_items: "Paket kerja ini belum dijadwalkan dalam agenda rapat mendatang." text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." - text_work_package_add_to_meeting_hint: 'Use the "Add to meeting" button to add this work package to an upcoming meeting.' - text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." - text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." - text_email_updates_enabled: "Email calendar updates are enabled. All participants will receive updated invites via email when you make changes." + text_work_package_add_to_meeting_hint: 'Gunakan tombol “Tambahkan ke rapat” untuk menambahkan paket kerja ini ke rapat yang akan datang.' + text_work_package_has_no_past_meeting_agenda_items: "Paket kerja ini tidak ditambahkan sebagai agenda dalam rapat sebelumnya." + text_email_updates_muted: "Pembaruan kalender melalui email telah dinonaktifkan. Peserta tidak akan menerima undangan yang diperbarui melalui email saat Anda melakukan perubahan." + text_email_updates_enabled: "Pembaruan kalender melalui email telah diaktifkan. Seluruh peserta akan menerima undangan yang diperbarui melalui email saat Anda melakukan perubahan." my_account: access_tokens: token/ical_meeting: - blank_description: "You can create one using the button below." - blank_title: "No iCalendar meeting token" - title: "iCalendar for meetings" - table_title: "iCalendar meeting tokens" - text_hint: "iCalendar meeting tokens allow users to subscribe to all their meetings and view up-to-date meeting information in external clients." - disabled_text: "iCalendar meeting subscriptions are not enabled by the administrator. Please contact your administrator to use this feature." - add_button: "Subscribe to calendar" + blank_description: "Anda dapat membuatnya melalui tombol di bawah ini." + blank_title: "Tidak ada token rapat iCalendar" + title: "Rapat untuk iCalendar" + table_title: "Token rapat iCalendar" + text_hint: "Token rapat iCalendar memungkinkan pengguna untuk berlangganan ke semua rapat mereka dan melihat informasi pertemuan terbaru di klien eksternal." + disabled_text: "Langganan rapat iCalendar tidak diaktifkan oleh administrator. Silakan hubungi administrator Anda untuk menggunakan fitur ini." + add_button: "Langganan ke kalender" my: access_token: dialog: token/ical_meeting: - dialog_title: "New iCal subscription token for meetings" - dialog_body: "This token will generate an iCal subscription URL that lets you view all your meetings in an external calendar application." - create_button: "Create subscription" - name_label: "Token name" - name_caption: 'You can name it after where you will use it, such as "My phone" or "Work computer".' + dialog_title: "Token langganan iCal baru untuk rapat" + dialog_body: "Token ini akan menghasilkan URL langganan iCal yang memungkinkan Anda melihat semua pertemuan Anda di aplikasi kalender eksternal." + create_button: "Buat langganan" + name_label: "Nama token" + name_caption: 'Anda dapat memberikan nama sesuai dengan tempat Anda akan menggunakannya, misalnya “Ponsel saya” atau “Komputer kerja”.' created_dialog: token/ical_meeting: - title: "An iCal meeting subscription token has been generated" - body: "Treat the following URL as you would a password. Anyone who has access to it can view all your meetings." + title: "Token langganan pertemuan iCal telah dibuat" + body: "Perlakukan URL berikut ini seperti Anda memperlakukan kata sandi. Siapa pun yang memiliki akses ke URL tersebut dapat melihat semua pertemuan Anda." revocation: token/ical_meeting: - notice_success: "The iCalendar meeting subscription has been revoked successfully." - notice_failure: "Failed to revoke iCalendar meeting subscription: %{error}" + notice_success: "Langganan pertemuan iCalendar telah dibatalkan dengan sukses." + notice_failure: "Gagal membatalkan langganan pertemuan iCalendar: %{error}" diff --git a/modules/meeting/config/locales/crowdin/js-ru.yml b/modules/meeting/config/locales/crowdin/js-ru.yml index 81d7bef3c1a..bce4861462f 100644 --- a/modules/meeting/config/locales/crowdin/js-ru.yml +++ b/modules/meeting/config/locales/crowdin/js-ru.yml @@ -24,4 +24,4 @@ ru: label_meetings: 'Совещания' work_packages: tabs: - meetings: 'Встречи' + meetings: 'Совещания' diff --git a/modules/meeting/config/locales/crowdin/ru.yml b/modules/meeting/config/locales/crowdin/ru.yml index 6b6c18d17cf..f407c89927c 100644 --- a/modules/meeting/config/locales/crowdin/ru.yml +++ b/modules/meeting/config/locales/crowdin/ru.yml @@ -246,15 +246,15 @@ ru: summary: "'%{title}' было отменено %{actor}." date_time: "Запланированная дата/время" participant_added: - header: "Meeting '%{title}' - Participant added" - header_series: "Meeting series '%{title}' - Participant added" - summary: "%{actor} added %{participant} to the meeting '%{title}'" - summary_series: "%{actor} added %{participant} to the meeting series '%{title}'" + header: "Совещание '%{title}' - участник добавлен" + header_series: "Серия совещаний '%{title}' - участник добавлен" + summary: "%{actor} добавил %{participant} в совещание '%{title}'" + summary_series: "%{actor} добавил %{participant} в серию совещаний '%{title}'" participant_removed: - header: "Meeting '%{title}' - Participant removed" - header_series: "Meeting series '%{title}' - Participant removed" - summary: "%{actor} removed %{participant} from the meeting '%{title}'" - summary_series: "%{actor} removed %{participant} from the meeting series '%{title}'" + header: "Совещание '%{title}' - участник удален" + header_series: "Серия совещаний '%{title}' - участник удален" + summary: "%{actor} удалил %{participant} из совещания '%{title}'" + summary_series: "%{actor} удалил %{participant} из серии совещаний '%{title}'" ended: header_series: "Завершено: Серия совещаний '%{title}'" summary_series: "Серия совещаний '%{title}' была завершена %{actor}." diff --git a/modules/meeting/config/locales/crowdin/zh-TW.yml b/modules/meeting/config/locales/crowdin/zh-TW.yml index 756abcc0c54..4cde114d109 100644 --- a/modules/meeting/config/locales/crowdin/zh-TW.yml +++ b/modules/meeting/config/locales/crowdin/zh-TW.yml @@ -231,15 +231,15 @@ zh-TW: summary: "%{title}' 已被 %{actor}取消。" date_time: "排定日期/時間" participant_added: - header: "Meeting '%{title}' - Participant added" - header_series: "Meeting series '%{title}' - Participant added" - summary: "%{actor} added %{participant} to the meeting '%{title}'" - summary_series: "%{actor} added %{participant} to the meeting series '%{title}'" + header: "會議 '%{title}' - 與會者" + header_series: "系列會議 '%{title}' - 與會者" + summary: "%{actor} 加入 %{participant} 至會議 '%{title}'" + summary_series: "%{actor} 加入 %{participant} 至會議系列 '%{title}'" participant_removed: - header: "Meeting '%{title}' - Participant removed" - header_series: "Meeting series '%{title}' - Participant removed" - summary: "%{actor} removed %{participant} from the meeting '%{title}'" - summary_series: "%{actor} removed %{participant} from the meeting series '%{title}'" + header: "會議 '%{title}' - 參與者已移除" + header_series: "會議系列 '%{title}' - 已移除參與者" + summary: "%{actor} 從會議中移除 %{participant} '%{title}'" + summary_series: "%{actor} 從會議系列中移除 %{participant} '%{title}'" ended: header_series: "結束:會議系列 '%{title}'" summary_series: "會議系列 '%{title}' 已由 %{actor}結束。" diff --git a/modules/openid_connect/config/locales/crowdin/id.yml b/modules/openid_connect/config/locales/crowdin/id.yml index 5b39d16c345..27d57dc9904 100644 --- a/modules/openid_connect/config/locales/crowdin/id.yml +++ b/modules/openid_connect/config/locales/crowdin/id.yml @@ -1,57 +1,57 @@ id: plugin_openproject_openid_connect: name: "OpenProject OpenID Connect" - description: "Adds OmniAuth OpenID Connect strategy providers to OpenProject." + description: "Menambahkan penyedia strategi OmniAuth OpenID Connect ke OpenProject." logout_warning: > Anda telah keluar. Isi formulir apa pun yang Anda kirim mungkin akan hilang. Silahkan [log in]. activerecord: attributes: openid_connect/group_link: - oidc_group_name: OpenID group identifier + oidc_group_name: Pengenal grup OpenID openid_connect/provider: name: Nama - slug: Unique identifier + slug: Pengenal unik display_name: Nama tampilan client_id: ID Pelanggan client_secret: Rahasia klien - groups_claim: Groups claim - group_regexes: Patterns (regular expressions) + groups_claim: Klaim grup + group_regexes: Pola (ekspresi reguler) secret: Kunci Rahasia scope: Cakupan - sync_groups: Synchronize groups - grant_types_supported: Supported grant types + sync_groups: Sinkronisasi grup + grant_types_supported: Jenis bantuan yang didukung limit_self_registration: Batasi pendaftaran mandiri - authorization_endpoint: Authorization endpoint - userinfo_endpoint: User information endpoint + authorization_endpoint: Otorisasi endpoint + userinfo_endpoint: Informasi pengguna endpoint token_endpoint: Token endpoint - end_session_endpoint: End session endpoint - post_logout_redirect_uri: Post logout redirect URI + end_session_endpoint: Akhiri sesi endpoint + post_logout_redirect_uri: URI pengalihan setelah keluar jwks_uri: JWKS URI - issuer: Issuer - tenant: Tenant + issuer: Penerbit + tenant: Penyewa metadata_url: URL Metadata icon: Ikon kustom - claims: Claims - acr_values: ACR values - redirect_url: Redirect URL + claims: Klaim + acr_values: Nilai ACR + redirect_url: Alihkan URL errors: models: openid_connect/provider: attributes: metadata_url: - format: "Discovery endpoint URL %{message}" - response_is_not_successful: " responds with %{status}." - response_is_not_json: " does not return JSON body." - response_misses_required_attributes: " does not return required attributes. Missing attributes are: %{missing_attributes}." - invalid_claims_essential: "does not define a boolean at %{attribute}." - invalid_claims_location: "contain unsupported locations: %{invalid}. Supported locations are: %{supported}." - invalid_claims_values: "does not define an array at %{attribute}." - non_object_attribute: "does not define a JSON object at %{attribute}." + format: "Temukan URL endpoint %{message}" + response_is_not_successful: " merespon dengan %{status}." + response_is_not_json: " tidak mengembalikan badan JSON." + response_misses_required_attributes: " tidak mengembalikan atribut yang diperlukan. Atribut yang hilang adalah: %{missing_attributes}." + invalid_claims_essential: "tidak mendefinisikan nilai boolean pada %{attribute}." + invalid_claims_location: "mengandung lokasi yang tidak didukung: %{invalid}. Lokasi yang didukung adalah: %{supported}." + invalid_claims_values: "tidak mendefinisikan array di %{attribute}." + non_object_attribute: "tidak mendefinisikan objek JSON di %{attribute}." provider: delete_warning: - input_delete_confirmation: Enter the provider name %{name} to confirm deletion. - irreversible_notice: Deleting an SSO provider is an irreversible action. - provider: 'Are you sure you want to delete the SSO provider %{name}? To confirm this action please enter the name of the provider in the field below, this will:' + input_delete_confirmation: Masukkan nama penyedia %{name} untuk mengonfirmasi penghapusan. + irreversible_notice: Menghapus penyedia SSO adalah tindakan yang tidak dapat dibatalkan. + provider: 'Apakah Anda yakin ingin menghapus penyedia SSO %{name}? Untuk mengonfirmasi tindakan ini, silakan masukkan nama penyedia di bidang di bawah ini, hal ini akan:' delete_result_1: Remove the provider from the list of available providers. delete_result_user_count: zero: No users are currently using this provider. No further action is required. @@ -84,10 +84,10 @@ id: oidc_information: These values are needed to configure the OpenID Connect provider. client_id: This is the client ID given to you by your OpenID Connect provider client_secret: This is the client secret given to you by your OpenID Connect provider - limit_self_registration: If enabled, users can only register using this provider if configuration on the provider's end allows it. + limit_self_registration: Jika diaktifkan, pengguna hanya dapat mendaftar menggunakan penyedia ini jika konfigurasi di sisi penyedia mengizinkannya. display_name: Nama penyedia. Nama ini akan ditampilkan sebagai tombol login dan dalam daftar penyedia. tenant: 'Please replace the default tenant with your own if applicable. See this.' - 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: Jika Anda ingin meminta ruang lingkup khusus, Anda dapat menambahkan satu atau beberapa nilai ruang lingkup yang dipisahkan oleh spasi di sini. Untuk informasi lebih lanjut, lihat [dokumentasi OpenID Connect](docs_url). post_logout_redirect_uri: The URL the OpenID Connect provider should redirect to after a logout request. claims: > You can request additional claims for the userinfo and id token endpoints. Please see [our OpenID connect documentation](docs_url) for more information. @@ -131,7 +131,7 @@ id: label_configuration_details: Metadata label_client_details: Client details label_attribute_mapping: Pemetaan atribut - notice_created: A new OpenID provider was successfully created. + notice_created: Penyedia OpenID baru telah berhasil dibuat. client_details_description: Configuration details of OpenProject as an OIDC client no_results_table: Belum ada penyedia yang ditentukan. plural: penyedia OpenID diff --git a/modules/recaptcha/config/locales/crowdin/id.yml b/modules/recaptcha/config/locales/crowdin/id.yml index e7ad4a7387a..cda345e17dd 100644 --- a/modules/recaptcha/config/locales/crowdin/id.yml +++ b/modules/recaptcha/config/locales/crowdin/id.yml @@ -2,7 +2,7 @@ id: plugin_openproject_recaptcha: name: "OpenProject ReCaptcha" - description: "This module provides recaptcha checks during login." + description: "Modul ini menyediakan verifikasi reCAPTCHA selama proses login." recaptcha: label_recaptcha: "reCAPTCHA" button_please_wait: 'Harap tunggu ...' @@ -22,4 +22,4 @@ id: type_hcaptcha: 'HCaptcha' type_turnstile: 'Cloudflare Turnstile™' captcha_description_html: > - reCAPTCHA is a free service by Google that can be enabled for your OpenProject instance. If enabled, a captcha form will be rendered upon login for all users that have not verified a captcha yet.
Please see the following link for more details on reCAPTCHA and their versions, and how to create the website and secret keys: %{recaptcha_link}
HCaptcha is a Google-free alternative that you can use if you do not want to use reCAPTCHA. See this link for more information: %{hcaptcha_link}
Cloudflare Turnstile™ is another alternative that is more convenient for users while still providing the same level of security. See this link for more information: %{turnstile_link} + reCAPTCHA adalah layanan gratis dari Google yang dapat diaktifkan untuk instance OpenProject Anda. Jika diaktifkan, formulir captcha akan ditampilkan saat login bagi semua pengguna yang belum memverifikasi captcha.
Silakan lihat tautan berikut untuk detail lebih lanjut tentang reCAPTCHA dan versinya, serta cara membuat kunci situs web dan kunci rahasia: %{recaptcha_link}
HCaptcha adalah alternatif bebas Google yang dapat Anda gunakan jika tidak ingin menggunakan reCAPTCHA. Lihat tautan ini untuk informasi lebih lanjut: %{hcaptcha_link}
Cloudflare Turnstile™ adalah alternatif lain yang lebih nyaman bagi pengguna sambil tetap memberikan tingkat keamanan yang sama. Lihat tautan ini untuk informasi lebih lanjut: %{turnstile_link} diff --git a/modules/reporting/config/locales/crowdin/id.yml b/modules/reporting/config/locales/crowdin/id.yml index c71a3ce29b1..d79c98b7da6 100644 --- a/modules/reporting/config/locales/crowdin/id.yml +++ b/modules/reporting/config/locales/crowdin/id.yml @@ -21,8 +21,8 @@ #++ id: plugin_openproject_reporting: - name: "OpenProject Reporting" - description: "This plugin allows creating custom cost reports with filtering and grouping created by the OpenProject Time and costs plugin." + name: "Laporan OpenProject" + description: "Plugin ini memungkinkan pembuatan laporan biaya kustom dengan fitur penyaringan dan pengelompokan yang dibuat oleh plugin Waktu dan Biaya OpenProject." button_save_report_as: "Simpan sebagai..." comments: "Komentar" cost_reports_title: "Waktu dan biaya" @@ -89,17 +89,17 @@ id: validation_failure_integer: "validasi bilangan bulat gagal" export: timesheet: - title: "Your PDF timesheet export" + title: "Ekspor lembar waktu PDF Anda" button: "Ekspor absensi PDF" timesheet: "Absensi" time: "Waktu" - sums_hours: Sums - overview_per_user_total: "Overview: Total hours per user" + sums_hours: Jumlah + overview_per_user_total: "Ringkasan: Jumlah jam per pengguna" overview_per_user_day: "Gambaran umum: Jam per pengguna per hari" cost_reports: title: "Ekspor XLS Laporan Biaya Anda" start_time: "Waktu mulai" - end_time: "End time" + end_time: "Waktu selesai" reporting: group_by: selected_columns: "Kolom terpilih" diff --git a/modules/reporting/config/locales/crowdin/js-id.yml b/modules/reporting/config/locales/crowdin/js-id.yml index 85ee058e213..a09b16c9d2e 100644 --- a/modules/reporting/config/locales/crowdin/js-id.yml +++ b/modules/reporting/config/locales/crowdin/js-id.yml @@ -23,4 +23,4 @@ id: js: reporting_engine: label_remove: "Hapus" - label_response_error: "There was an error handling the query." + label_response_error: "Terjadi kesalahan saat memproses permintaan." diff --git a/modules/storages/config/locales/crowdin/zh-TW.yml b/modules/storages/config/locales/crowdin/zh-TW.yml index 32d31032ef6..c2e5cb16b0f 100644 --- a/modules/storages/config/locales/crowdin/zh-TW.yml +++ b/modules/storages/config/locales/crowdin/zh-TW.yml @@ -104,20 +104,20 @@ zh-TW: create_folder: '託管的專案資料夾建立:' ensure_root_folder_permissions: '設定基本資料夾權限:' hide_inactive_folders: '隱藏不活動的資料夾步驟:' - remote_folders: 'Read contents of the team folder:' + remote_folders: '閱讀團隊資料夾的內容:' remove_user_from_group: '從群組移除使用者:' rename_project_folder: '重新命名託管的專案資料夾:' one_drive_sync_service: create_folder: '託管的專案資料夾建立:' ensure_root_folder_permissions: '設定基本資料夾權限:' hide_inactive_folders: '隱藏不活動的資料夾步驟:' - remote_folders: 'Read contents of the drive root folder:' + remote_folders: '讀取磁碟機根資料夾的內容:' rename_project_folder: '重新命名託管的專案資料夾:' sharepoint_sync_service: create_folder: '管理專案資料夾建立:' ensure_root_folder_permissions: '設定基本資料夾權限:' hide_inactive_folders: '隱藏不活動的資料夾步驟:' - remote_folders: 'Read contents of the drive root folder:' + remote_folders: '讀取磁碟機根資料夾的內容:' rename_project_folder: '重新命名管理的專案資料夾:' errors: messages: @@ -140,7 +140,7 @@ zh-TW: conflict: 資料夾 %{folder_name} 已存在於 %{parent_location}上。 not_found: "%{parent_location} 沒有找到。" ensure_root_folder_permissions: - not_found: "%{group_folder} wasn't found. Please check your Nextcloud Team Folder setup." + not_found: "%{group_folder} 未找到。請檢查您的 Nextcloud Team Folder 設定。" permission_not_set: 無法在 %{group_folder} 上設定權限。 hide_inactive_folders: permission_not_set: 無法在 %{path} 上設定權限。 @@ -230,7 +230,7 @@ zh-TW: storage_delete_result_3: 自動管理的專案資料夾及其中的所有檔案都會刪除 dependencies: nextcloud: - group_folders_app: Team Folders + group_folders_app: 團隊資料夾 integration_app: 整合 OpenProject enabled_in_projects: setup_incomplete_description: 此儲存的設定不完整。請先完成設定,再於多個專案中啟用。 @@ -277,11 +277,11 @@ zh-TW: client_folder_creation: 自動新增資料夾 client_folder_removal: 自動刪除資料夾 drive_contents: 磁碟內容 - files_request: Fetching team folder files + files_request: 擷取團隊資料夾檔案 header: 自動管理的專案資料夾 - team_folder_app: 'Dependency: Team Folders' - team_folder_contents: Team folder content - team_folder_presence: Team folder exists + team_folder_app: '依賴性:團隊文件夾' + team_folder_contents: 團隊資料夾內容 + team_folder_presence: 團隊資料夾存在 userless_access: 服務器端要求認證 authentication: existing_token: 用戶令牌(Token) @@ -320,8 +320,8 @@ zh-TW: nc_oauth_request_not_found: 無法找到取得目前連線使用者的端點。請檢查伺服器日誌以取得更多資訊。 nc_oauth_request_unauthorized: 當前使用者未獲授權存取遠端檔案儲存空間。請檢查伺服器日誌以取得進一步資訊。 nc_oauth_token_missing: OpenProject 無法測試與 Nextcloud 的用戶級連線,因為沒有當前用戶的令牌(Token)。 - nc_team_folder_not_found: The team folder could not be found. - nc_unexpected_content: Unexpected content found in the managed team folder. + nc_team_folder_not_found: 找不到團隊資料夾。 + nc_unexpected_content: 在管理的團隊資料夾中發現意外內容。 nc_userless_access_denied: 設定的應用程式密碼無效。 not_configured: 連線無法驗證。請先完成設定。 od_client_cant_delete_folder: 用戶刪除資料夾失敗,請檢查儲存空間的文件設定。 diff --git a/modules/team_planner/config/locales/crowdin/id.yml b/modules/team_planner/config/locales/crowdin/id.yml index 6eb0756ec9d..77a65c3fa72 100644 --- a/modules/team_planner/config/locales/crowdin/id.yml +++ b/modules/team_planner/config/locales/crowdin/id.yml @@ -1,8 +1,8 @@ #English strings go here id: plugin_openproject_team_planner: - name: "OpenProject Team Planner" - description: "Provides team planner views." + name: "Perencana Tim OpenProject" + description: "Menyediakan tampilan perencanaan tim." permission_view_team_planner: "Lihat rencana tim" permission_manage_team_planner: "Kelola rencana tim" project_module_team_planner_view: "Rencana tim" diff --git a/modules/team_planner/config/locales/crowdin/js-id.yml b/modules/team_planner/config/locales/crowdin/js-id.yml index 8eced716f47..349ca180f96 100644 --- a/modules/team_planner/config/locales/crowdin/js-id.yml +++ b/modules/team_planner/config/locales/crowdin/js-id.yml @@ -2,28 +2,28 @@ id: js: team_planner: - add_existing: 'Add existing' + add_existing: 'Tambahkan yang sudah ada' label_team_planner_plural: 'Rencana tim' - add_existing_title: 'Add existing work packages' + add_existing_title: 'Tambahkan paket kerja yang sudah ada' create_label: 'Rencana tim' create_title: 'Buat rencana tim baru' - unsaved_title: 'Unnamed team planner' - no_data: 'Add assignees to set up your team planner.' + unsaved_title: 'Perencana tim tanpa nama' + no_data: 'Tambahkan penanggung jawab untuk mengatur perencana tim Anda.' add_assignee: 'Pelimpahan' - remove_assignee: 'Remove assignee' - two_weeks: '2-week' - one_week: '1-week' - four_weeks: '4-week' - eight_weeks: '8-week' - work_week: 'Work week' - today: 'Today' - drag_here_to_remove: 'Drag here to remove assignee and start and end dates.' - cannot_drag_here: 'Cannot move the work package due to permissions or editing restrictions.' - cannot_drag_to_non_working_day: 'This work package cannot start/finish on a non-working day.' + remove_assignee: 'Hapus penanggung jawab' + two_weeks: '2 minggu' + one_week: '1 minggu' + four_weeks: '4 minggu' + eight_weeks: '8 minggu' + work_week: 'Minggu kerja' + today: 'Hari Ini' + drag_here_to_remove: 'Seret ke sini untuk menghapus penanggung jawab dan tanggal mulai dan tanggal berakhir.' + cannot_drag_here: 'Tidak dapat memindahkan paket kerja karena batasan izin atau pembatasan penuntingan.' + cannot_drag_to_non_working_day: 'Paket kerja ini tidak dapat dimulai/diselesaikan pada hari libur.' quick_add: - empty_state: 'Use the search field to find work packages and drag them to the planner to assign it to someone and define start and end dates.' - search_placeholder: 'Search...' + empty_state: 'Gunakan bidang pencarian untuk menemukan paket pekerjaan dan seret ke dalam planner untuk menugaskan kepada seseorang dan menentukan tanggal mulai dan berakhir.' + search_placeholder: 'Cari...' modify: errors: - permission_denied: 'You do not have the necessary permissions to modify this.' - fallback: 'This work package cannot be edited.' + permission_denied: 'Anda tidak memiliki izin yang diperlukan untuk mengubah ini.' + fallback: 'Paket kerja ini tidak dapat disunting.' diff --git a/modules/two_factor_authentication/config/locales/crowdin/id.yml b/modules/two_factor_authentication/config/locales/crowdin/id.yml index 38d5dddabe8..722cafb5506 100644 --- a/modules/two_factor_authentication/config/locales/crowdin/id.yml +++ b/modules/two_factor_authentication/config/locales/crowdin/id.yml @@ -1,20 +1,20 @@ #English strings go here for Rails i18n id: plugin_openproject_two_factor_authentication: - name: "OpenProject Two-factor authentication" + name: "Otentikasi dua faktor OpenProject" description: >- - This OpenProject plugin authenticates your users using two-factor authentication by means of one-time password through the TOTP standard (Google Authenticator) or sent to the user's cell phone via SMS or voice call. + Plugin OpenProject ini mengautentikasi pengguna Anda menggunakan otentikasi dua faktor melalui kata sandi sekali pakai (one-time password) berdasarkan standar TOTP (Google Authenticator) atau dikirim ke ponsel pengguna melalui SMS atau panggilan suara. activerecord: attributes: two_factor_authentication/device: identifier: "Pengidentifikasi" default: "Digunakan sebagai standar" - channel: "Channel" + channel: "Saluran" two_factor_authentication/device/sms: phone_number: "Nomor telepon" two_factor_authentication/device/webauthn: - webauthn_external: "WebAuthn ID" - webauthn_public_key: "WebAuthn public key" + webauthn_external: "ID WebAuthn" + webauthn_public_key: "Kunci publik WebAuthn" errors: models: two_factor_authentication/device: @@ -30,7 +30,7 @@ id: two_factor_authentication/device/webauthn: "WebAuthn" two_factor_authentication: error_2fa_disabled: "Pengiriman 2FA telah dinonaktifkan." - notice_not_writable: "2FA Settings were provided through environment variables and cannot be overwritten." + notice_not_writable: "Pengaturan 2FA disediakan melalui variabel lingkungan dan tidak dapat diubah." error_no_device: "Tidak ada perangkat 2FA terdaftar yang ditemukan untuk pengguna ini, meskipun yang diperlukan untuk contoh ini." error_no_matching_strategy: "Tidak ada 2FA strategi yang cocok tersedia untuk pengguna ini. Silahkan hubungi administratior Anda." error_is_enforced_not_active: "Kesalahan konfigurasi: dua faktor otentikasi yang telah diberlakukan, tapi ada tidak ada strategi yang aktif." @@ -46,8 +46,8 @@ id: label_one_time_password: "Satu-kali kata sandi" label_2fa_enabled: "Dua faktor otentikasi tidak aktif" label_2fa_disabled: "Dua faktor otentikasi tidak aktif" - text_otp_delivery_message_sms: "Your %{app_title} one-time password is %{token}" - text_otp_delivery_message_voice: "Your %{app_title} one-time password is: %{pause} %{token}. %{pause} I repeat: %{pause} %{token}" + text_otp_delivery_message_sms: "Kata sandi sekali pakai untuk %{app_title} Anda adalah %{token}" + text_otp_delivery_message_voice: "Kata sandi sekali pakai untuk %{app_title} Anda adalah: %{pause} %{token}. %{pause} Saya ulangi: %{pause} %{token}" text_enter_2fa: "Silakan masukkan sandi satu kali dari perangkat Anda." text_2fa_enabled: "Setelah setiap login, Anda akan diminta untuk memasukkan tanda OTP dari perangkat 2FA default Anda." text_2fa_disabled: "Untuk mengaktifkan otentikasi dua faktor, gunakan tombol di atas untuk mendaftar perangkat 2FA baru. Jika Anda sudah memiliki perangkat, Anda perlu untuk membuatnya default." @@ -78,12 +78,12 @@ id: delete_all_are_you_sure: "Apakah Anda yakin Anda ingin menghapus semua 2FA perangkat untuk pengguna ini?" button_delete_all_devices: "Menghapus perangkat terdaftar 2FA" button_register_mobile_phone_for_user: "Daftar ponsel" - text_2fa_enabled: "Upon every login, this user will be requested to enter a OTP token from their default 2FA device." - text_2fa_disabled: "The user did not set up a 2FA device through their 'My account page'" - only_sms_allowed: "Only SMS delivery can be set up for other users." + text_2fa_enabled: "Setiap kali login, pengguna ini akan diminta untuk memasukkan token OTP dari perangkat 2FA default mereka." + text_2fa_disabled: "Pengguna tidak mengatur perangkat 2FA melalui halaman ‘Akun Saya’ mereka" + only_sms_allowed: "Pengiriman SMS hanya dapat diatur untuk pengguna lain." upsell: title: "Autentikasi dua faktor" - description: "Strenghten the security of your OpenProject instance by offering (or enforcing) two-factor authentification to all project members." + description: "Perkuat keamanan instance OpenProject Anda dengan menawarkan (atau mewajibkan) autentikasi dua faktor kepada semua anggota proyek." backup_codes: none_found: Tidak ada cadangan yang ada untuk akun ini. singular: Kode cadangan @@ -117,14 +117,14 @@ id: failed_to_delete: "Gagal untuk menghapus perangkat 2FA." is_default_cannot_delete: "Perangkat ini ditandai sebagai default dan tidak dapat dihapus karena kebijakan keamanan aktif. Menandai perangkat lain sebagai default sebelum menghapus." not_existing: "Tidak ada perangkat 2FA telah terdaftar untuk akun Anda." - 2fa_from_input: Please enter the code from your %{device_name} to verify your identity. - 2fa_from_webauthn: Please provide the WebAuthn device %{device_name}. If it is USB based make sure to plug it in and touch it. Then click the sign in button. + 2fa_from_input: Silakan masukkan kode dari perangkat Anda %{device_name} untuk memverifikasi identitas Anda. + 2fa_from_webauthn: Silakan sediakan perangkat WebAuthn %{device_name}. Jika perangkat tersebut berbasis USB, pastikan untuk mencolokkannya dan menyentuhnya. Kemudian klik tombol masuk. webauthn: title: "WebAuthn" - description: Register a FIDO2 device (like YubiKey) or the secure enclave of your mobile device. - further_steps: After you have chosen a name, you can click the Continue button. Your browser will prompt you to present your WebAuthn device. When you have done so, you are done registering the device. + description: Daftarkan perangkat FIDO2 (seperti YubiKey) atau enklaf aman perangkat seluler Anda. + further_steps: Setelah Anda memilih nama, Anda dapat mengklik tombol Lanjutkan. Browser Anda akan meminta Anda untuk menampilkan perangkat WebAuthn Anda. Setelah Anda melakukannya, pendaftaran perangkat telah selesai. totp: - title: "App-based authenticator" + title: "Autentikator berbasis aplikasi" provisioning_uri: "Penyediaan URI" secret_key: "Kunci rahasia" time_based: "Berdasarkan waktu" @@ -136,13 +136,13 @@ id: text_cannot_scan: | Jika Anda tidak dapat memindai kode, Anda dapat memasukkan entri manual menggunakan rincian berikut: description: | - Use a one-time code generated by an authenticator like Authy or Google Authenticator. + Gunakan kode sekali pakai yang dihasilkan oleh aplikasi autentikator seperti Authy atau Google Authenticator. sms: - title: "Mobile device" + title: "Perangkat seluler" redacted_identifier: "Perangkat mobile (%{redacted_number})" request_2fa_identifier: "%{redacted_identifier}, kami mengirimkan kode otentikasi melalui %{delivery_channel}" description: | - Receive 2FA code via a text message on your phone each time you log in. + Dapatkan kode 2FA melalui pesan teks di ponsel Anda setiap kali Anda masuk. sns: delivery_failed: "Pengiriman SNS gagal:" message_bird: @@ -160,10 +160,10 @@ id: remember: active_session_notice: > Akun Anda telah aktif ingat cookie yang berlaku hingga %{expires_on}. Cookie ini memungkinkan Anda untuk masuk tanpa faktor kedua ke akun Anda sehingga waktu itu. - other_active_session_notice: Your account has an active remember cookie on another session. + other_active_session_notice: Akun Anda memiliki pengingat kuki yang aktif di sesi lain. label: "ingat" - clear_cookie: "Click here to remove all remembered 2FA sessions." - cookie_removed: "All remembered 2FA sessions have been removed." + clear_cookie: "Klik di sini untuk menghapus semua sesi 2FA yang tersimpan." + cookie_removed: "Semua sesi 2FA yang tersimpan telah dihapus." dont_ask_again: "Membuat cookie untuk mengingat 2FA otentikasi pada klien ini untuk %{days} hari." field_phone: "Ponsel" field_otp: "Satu-kali kata sandi" diff --git a/modules/two_factor_authentication/config/locales/crowdin/js-id.yml b/modules/two_factor_authentication/config/locales/crowdin/js-id.yml index 814998db2a7..80b59bc2653 100644 --- a/modules/two_factor_authentication/config/locales/crowdin/js-id.yml +++ b/modules/two_factor_authentication/config/locales/crowdin/js-id.yml @@ -23,4 +23,4 @@ id: js: two_factor_authentication: errors: - aborted: "The authentication was cancelled. Please try again." + aborted: "Autentikasi telah dibatalkan. Silakan coba lagi." diff --git a/modules/webhooks/config/locales/crowdin/id.yml b/modules/webhooks/config/locales/crowdin/id.yml index 291390883ee..6eb68aafb3c 100644 --- a/modules/webhooks/config/locales/crowdin/id.yml +++ b/modules/webhooks/config/locales/crowdin/id.yml @@ -8,7 +8,7 @@ id: url: 'URL muatan' secret: 'Rahasia tanda tangan' events: 'Acara' - enabled: 'Enabled' + enabled: 'Aktifkan' projects: 'Proyek-proyek yang diaktifkan' webhooks/log: event_name: 'Nama acara' @@ -28,14 +28,14 @@ id: label_add_new: Tambahkan webhook baru label_edit: Ubah webhook label_x_events: - one: "1 event" - other: "%{count} events" - zero: "No events" + one: "1 acara" + other: "%{count} acara" + zero: "Tidak ada acara" events: created: "Dibuat" updated: "Diperbarui" comment: "Komentar" - internal_comment: "Internal comment" + internal_comment: "Komentar internal" explanation: text: > Setelah kejadian seperti pembuatan paket kerja atau pembaruan proyek, OpenProject akan mengirimkan permintaan POST ke titik akhir web yang dikonfigurasi. Sering kali, acara dikirim setelah %{link} lewat. From 1bf2e845d702db880ad8e78f1cb9bd803928ffe0 Mon Sep 17 00:00:00 2001 From: Eric Schubert Date: Fri, 30 Jan 2026 09:40:36 +0100 Subject: [PATCH 034/293] [chore] removed .env files from git tracking --- docker/dev/gitlab/.env | 1 - docker/dev/gitlab/.gitignore | 1 + docker/dev/hocuspocus/.env | 1 - docker/dev/hocuspocus/.gitignore | 1 + docker/dev/keycloak/.env | 1 - docker/dev/keycloak/.gitignore | 1 + docker/dev/minio/.env | 1 - docker/dev/minio/.gitignore | 1 + docker/dev/nextcloud/.env | 1 - docker/dev/nextcloud/.gitignore | 1 + docker/dev/tls/.env | 1 - docker/dev/tls/.gitignore | 2 +- 12 files changed, 6 insertions(+), 7 deletions(-) delete mode 100644 docker/dev/gitlab/.env delete mode 100644 docker/dev/hocuspocus/.env delete mode 100644 docker/dev/keycloak/.env delete mode 100644 docker/dev/minio/.env delete mode 100644 docker/dev/nextcloud/.env delete mode 100644 docker/dev/tls/.env diff --git a/docker/dev/gitlab/.env b/docker/dev/gitlab/.env deleted file mode 100644 index c962deb2abe..00000000000 --- a/docker/dev/gitlab/.env +++ /dev/null @@ -1 +0,0 @@ -OPENPROJECT_DOCKER_DEV_TLD=local diff --git a/docker/dev/gitlab/.gitignore b/docker/dev/gitlab/.gitignore index 7376571d14b..b8865edfb36 100644 --- a/docker/dev/gitlab/.gitignore +++ b/docker/dev/gitlab/.gitignore @@ -1 +1,2 @@ +.env docker-compose.override.yml diff --git a/docker/dev/hocuspocus/.env b/docker/dev/hocuspocus/.env deleted file mode 100644 index c962deb2abe..00000000000 --- a/docker/dev/hocuspocus/.env +++ /dev/null @@ -1 +0,0 @@ -OPENPROJECT_DOCKER_DEV_TLD=local diff --git a/docker/dev/hocuspocus/.gitignore b/docker/dev/hocuspocus/.gitignore index 7376571d14b..b8865edfb36 100644 --- a/docker/dev/hocuspocus/.gitignore +++ b/docker/dev/hocuspocus/.gitignore @@ -1 +1,2 @@ +.env docker-compose.override.yml diff --git a/docker/dev/keycloak/.env b/docker/dev/keycloak/.env deleted file mode 100644 index c962deb2abe..00000000000 --- a/docker/dev/keycloak/.env +++ /dev/null @@ -1 +0,0 @@ -OPENPROJECT_DOCKER_DEV_TLD=local diff --git a/docker/dev/keycloak/.gitignore b/docker/dev/keycloak/.gitignore index 7376571d14b..b8865edfb36 100644 --- a/docker/dev/keycloak/.gitignore +++ b/docker/dev/keycloak/.gitignore @@ -1 +1,2 @@ +.env docker-compose.override.yml diff --git a/docker/dev/minio/.env b/docker/dev/minio/.env deleted file mode 100644 index c962deb2abe..00000000000 --- a/docker/dev/minio/.env +++ /dev/null @@ -1 +0,0 @@ -OPENPROJECT_DOCKER_DEV_TLD=local diff --git a/docker/dev/minio/.gitignore b/docker/dev/minio/.gitignore index 7376571d14b..b8865edfb36 100644 --- a/docker/dev/minio/.gitignore +++ b/docker/dev/minio/.gitignore @@ -1 +1,2 @@ +.env docker-compose.override.yml diff --git a/docker/dev/nextcloud/.env b/docker/dev/nextcloud/.env deleted file mode 100644 index c962deb2abe..00000000000 --- a/docker/dev/nextcloud/.env +++ /dev/null @@ -1 +0,0 @@ -OPENPROJECT_DOCKER_DEV_TLD=local diff --git a/docker/dev/nextcloud/.gitignore b/docker/dev/nextcloud/.gitignore index 7376571d14b..b8865edfb36 100644 --- a/docker/dev/nextcloud/.gitignore +++ b/docker/dev/nextcloud/.gitignore @@ -1 +1,2 @@ +.env docker-compose.override.yml diff --git a/docker/dev/tls/.env b/docker/dev/tls/.env deleted file mode 100644 index c962deb2abe..00000000000 --- a/docker/dev/tls/.env +++ /dev/null @@ -1 +0,0 @@ -OPENPROJECT_DOCKER_DEV_TLD=local diff --git a/docker/dev/tls/.gitignore b/docker/dev/tls/.gitignore index 5502086abc8..3211e03f17c 100644 --- a/docker/dev/tls/.gitignore +++ b/docker/dev/tls/.gitignore @@ -1,3 +1,3 @@ +.env acme.json - docker-compose.override.yml From 27095f9635393e974d3dd939b20859518d05b761 Mon Sep 17 00:00:00 2001 From: Eric Schubert Date: Fri, 30 Jan 2026 13:41:29 +0100 Subject: [PATCH 035/293] [chore] add fallback to usage of the dev tld --- docker-compose.yml | 2 +- docker/dev/gitlab/docker-compose.yml | 4 ++-- docker/dev/hocuspocus/docker-compose.yml | 4 ++-- docker/dev/keycloak/docker-compose.yml | 4 ++-- docker/dev/minio/docker-compose.yml | 4 ++-- docker/dev/nextcloud/docker-compose.yml | 2 +- .../docker-compose.core-override.example.yml | 18 ++++++++--------- docker/dev/tls/docker-compose.yml | 20 +++++++++---------- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 12ef9b8fb3a..f530e2fe00d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -87,7 +87,7 @@ services: networks: - network environment: - __VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS: "openproject-assets.${OPENPROJECT_DOCKER_DEV_TLD}" + __VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS: "openproject-assets.${OPENPROJECT_DOCKER_DEV_TLD:-local}" ports: - "${FE_PORT:-4200}:4200" diff --git a/docker/dev/gitlab/docker-compose.yml b/docker/dev/gitlab/docker-compose.yml index 42ad0f3eb6a..7cf56fd9101 100644 --- a/docker/dev/gitlab/docker-compose.yml +++ b/docker/dev/gitlab/docker-compose.yml @@ -19,10 +19,10 @@ services: networks: - external extra_hosts: - - "openproject.${OPENPROJECT_DOCKER_DEV_TLD}:host-gateway" + - "openproject.${OPENPROJECT_DOCKER_DEV_TLD:-local}:host-gateway" labels: - "traefik.enable=true" - - "traefik.http.routers.gitlab.rule=Host(`gitlab.${OPENPROJECT_DOCKER_DEV_TLD}`)" + - "traefik.http.routers.gitlab.rule=Host(`gitlab.${OPENPROJECT_DOCKER_DEV_TLD:-local}`)" - "traefik.http.routers.gitlab.entrypoints=websecure" - "traefik.http.services.gitlab.loadbalancer.server.port=80" diff --git a/docker/dev/hocuspocus/docker-compose.yml b/docker/dev/hocuspocus/docker-compose.yml index 4419c6e4365..6ff6d5fbda9 100644 --- a/docker/dev/hocuspocus/docker-compose.yml +++ b/docker/dev/hocuspocus/docker-compose.yml @@ -5,7 +5,7 @@ services: image: openproject/hocuspocus:latest labels: - "traefik.enable=true" - - "traefik.http.routers.hocuspocus.rule=Host(`hocuspocus.${OPENPROJECT_DOCKER_DEV_TLD}`)" + - "traefik.http.routers.hocuspocus.rule=Host(`hocuspocus.${OPENPROJECT_DOCKER_DEV_TLD:-local}`)" - "traefik.http.routers.hocuspocus.service=hocuspocus-service" - "traefik.http.routers.hocuspocus.tls=true" - "traefik.http.services.hocuspocus-service.loadbalancer.server.port=1234" @@ -14,7 +14,7 @@ services: networks: - gateway environment: - - ALLOWED_DOMAINS=openproject.${OPENPROJECT_DOCKER_DEV_TLD},localhost + - ALLOWED_DOMAINS=openproject.${OPENPROJECT_DOCKER_DEV_TLD:-local},localhost - NODE_TLS_REJECT_UNAUTHORIZED=0 - SECRET=secret12345 networks: diff --git a/docker/dev/keycloak/docker-compose.yml b/docker/dev/keycloak/docker-compose.yml index 4c5bf556edc..9c2f933ea8b 100644 --- a/docker/dev/keycloak/docker-compose.yml +++ b/docker/dev/keycloak/docker-compose.yml @@ -35,7 +35,7 @@ services: - KEYCLOAK_ADMIN=admin - KEYCLOAK_ADMIN_PASSWORD=admin - KC_DB_SCHEMA=public - - KC_HOSTNAME=keycloak.${OPENPROJECT_DOCKER_DEV_TLD} + - KC_HOSTNAME=keycloak.${OPENPROJECT_DOCKER_DEV_TLD:-local} - KC_TRANSACTION_XA_ENABLED=false volumes: - /etc/ssl/certs/ca-certificates.crt:/etc/ssl/certs/ca-certificates.crt:ro @@ -43,7 +43,7 @@ services: - ./themes/:/opt/keycloak/themes/ labels: - "traefik.enable=true" - - "traefik.http.routers.keycloak-sub-secure.rule=Host(`keycloak.${OPENPROJECT_DOCKER_DEV_TLD}`)" + - "traefik.http.routers.keycloak-sub-secure.rule=Host(`keycloak.${OPENPROJECT_DOCKER_DEV_TLD:-local}`)" - "traefik.http.routers.keycloak-sub-secure.entrypoints=websecure" depends_on: - db-keycloak diff --git a/docker/dev/minio/docker-compose.yml b/docker/dev/minio/docker-compose.yml index cecc8ee8df9..7255785f13c 100644 --- a/docker/dev/minio/docker-compose.yml +++ b/docker/dev/minio/docker-compose.yml @@ -19,13 +19,13 @@ services: - "traefik.enable=true" # MinIO API - "traefik.http.routers.minio.entrypoints=websecure" - - "traefik.http.routers.minio.rule=Host(`minio.${OPENPROJECT_DOCKER_DEV_TLD}`)" + - "traefik.http.routers.minio.rule=Host(`minio.${OPENPROJECT_DOCKER_DEV_TLD:-local}`)" - "traefik.http.routers.minio.service=minio" - "traefik.http.routers.minio.tls.certresolver=step" - "traefik.http.services.minio.loadbalancer.server.port=9000" # MinIO Admin Console (Management UI) - "traefik.http.routers.minioadmin.entrypoints=websecure" - - "traefik.http.routers.minioadmin.rule=Host(`minioadmin.${OPENPROJECT_DOCKER_DEV_TLD}`)" + - "traefik.http.routers.minioadmin.rule=Host(`minioadmin.${OPENPROJECT_DOCKER_DEV_TLD:-local}`)" - "traefik.http.routers.minioadmin.service=minioadmin" - "traefik.http.routers.minioadmin.tls.certresolver=step" - "traefik.http.services.minioadmin.loadbalancer.server.port=9001" diff --git a/docker/dev/nextcloud/docker-compose.yml b/docker/dev/nextcloud/docker-compose.yml index 100a37dbc49..19667ff8b2f 100644 --- a/docker/dev/nextcloud/docker-compose.yml +++ b/docker/dev/nextcloud/docker-compose.yml @@ -11,7 +11,7 @@ services: # - ../nextcloud_apps:/var/www/html/custom_apps labels: - "traefik.enable=true" - - "traefik.http.routers.nextcloud.rule=Host(`nextcloud.${OPENPROJECT_DOCKER_DEV_TLD}`)" + - "traefik.http.routers.nextcloud.rule=Host(`nextcloud.${OPENPROJECT_DOCKER_DEV_TLD:-local}`)" - "traefik.http.routers.nextcloud.entrypoints=websecure" cron: diff --git a/docker/dev/tls/docker-compose.core-override.example.yml b/docker/dev/tls/docker-compose.core-override.example.yml index d45a9be091b..70c0d67a745 100644 --- a/docker/dev/tls/docker-compose.core-override.example.yml +++ b/docker/dev/tls/docker-compose.core-override.example.yml @@ -6,30 +6,30 @@ x-op-env-override: &environment SSL_CERT_FILE: /etc/ssl/certs/ca-certificates.crt # uncomment and set all the envs below to integrate keycloak with OpenProject # OPENPROJECT_OPENID__CONNECT_KEYCLOAK_DISPLAY__NAME: Keycloak - # OPENPROJECT_OPENID__CONNECT_KEYCLOAK_HOST: keycloak.${OPENPROJECT_DOCKER_DEV_TLD} - # OPENPROJECT_OPENID__CONNECT_KEYCLOAK_IDENTIFIER: https://openproject.${OPENPROJECT_DOCKER_DEV_TLD} + # OPENPROJECT_OPENID__CONNECT_KEYCLOAK_HOST: keycloak.${OPENPROJECT_DOCKER_DEV_TLD:-local} + # OPENPROJECT_OPENID__CONNECT_KEYCLOAK_IDENTIFIER: https://openproject.${OPENPROJECT_DOCKER_DEV_TLD:-local} # OPENPROJECT_OPENID__CONNECT_KEYCLOAK_SECRET: - # OPENPROJECT_OPENID__CONNECT_KEYCLOAK_ISSUER: https://keycloak.${OPENPROJECT_DOCKER_DEV_TLD}/realms/ + # OPENPROJECT_OPENID__CONNECT_KEYCLOAK_ISSUER: https://keycloak.${OPENPROJECT_DOCKER_DEV_TLD:-local}/realms/ # OPENPROJECT_OPENID__CONNECT_KEYCLOAK_AUTHORIZATION__ENDPOINT: /realms//protocol/openid-connect/auth # OPENPROJECT_OPENID__CONNECT_KEYCLOAK_TOKEN__ENDPOINT: /realms//protocol/openid-connect/token # OPENPROJECT_OPENID__CONNECT_KEYCLOAK_USERINFO__ENDPOINT: /realms//protocol/openid-connect/userinfo - # OPENPROJECT_OPENID__CONNECT_KEYCLOAK_END__SESSION__ENDPOINT: https://keycloak.${OPENPROJECT_DOCKER_DEV_TLD}/realms//protocol/openid-connect/logout + # OPENPROJECT_OPENID__CONNECT_KEYCLOAK_END__SESSION__ENDPOINT: https://keycloak.${OPENPROJECT_DOCKER_DEV_TLD:-local}/realms//protocol/openid-connect/logout # uncomment the following for using minio (local S3) as file storage with TLS support: # OPENPROJECT_ATTACHMENTS__STORAGE: "fog" # OPENPROJECT_FOG_DIRECTORY: "openproject-uploads" # OPENPROJECT_FOG_CREDENTIALS_PROVIDER: "AWS" # Minio is S3 compliant, so we can use the AWS provider - # OPENPROJECT_FOG_CREDENTIALS_ENDPOINT: "https://minio.${OPENPROJECT_DOCKER_DEV_TLD}" + # OPENPROJECT_FOG_CREDENTIALS_ENDPOINT: "https://minio.${OPENPROJECT_DOCKER_DEV_TLD:-local}" # OPENPROJECT_FOG_CREDENTIALS_AWS__ACCESS__KEY__ID: "minioadmin" # OPENPROJECT_FOG_CREDENTIALS_AWS__SECRET__ACCESS__KEY: "minioadmin" # OPENPROJECT_FOG_CREDENTIALS_PATH__STYLE: "true" # OPENPROJECT_FOG_CREDENTIALS_REGION: "us-east-1" - # OPENPROJECT_DEV_EXTRA_HOSTS: "${OPENPROJECT_DEV_HOST},minio.${OPENPROJECT_DOCKER_DEV_TLD}" + # OPENPROJECT_DEV_EXTRA_HOSTS: "${OPENPROJECT_DEV_HOST},minio.${OPENPROJECT_DOCKER_DEV_TLD:-local}" services: backend: environment: <<: *environment - OPENPROJECT_CLI_PROXY: "https://openproject-assets.${OPENPROJECT_DOCKER_DEV_TLD}" + OPENPROJECT_CLI_PROXY: "https://openproject-assets.${OPENPROJECT_DOCKER_DEV_TLD:-local}" networks: - external volumes: @@ -42,7 +42,7 @@ services: # - ~/.step/certs:/usr/local/share/ca-certificates labels: - "traefik.enable=true" - - "traefik.http.routers.openproject.rule=Host(`openproject.${OPENPROJECT_DOCKER_DEV_TLD}`)" + - "traefik.http.routers.openproject.rule=Host(`openproject.${OPENPROJECT_DOCKER_DEV_TLD:-local}`)" - "traefik.http.routers.openproject.entrypoints=websecure" worker: @@ -77,7 +77,7 @@ services: - external labels: - "traefik.enable=true" - - "traefik.http.routers.openproject-assets.rule=Host(`openproject-assets.${OPENPROJECT_DOCKER_DEV_TLD}`)" + - "traefik.http.routers.openproject-assets.rule=Host(`openproject-assets.${OPENPROJECT_DOCKER_DEV_TLD:-local}`)" - "traefik.http.routers.openproject-assets.entrypoints=websecure" # You need to define the same external network diff --git a/docker/dev/tls/docker-compose.yml b/docker/dev/tls/docker-compose.yml index 1d4542081c1..65e24b97813 100644 --- a/docker/dev/tls/docker-compose.yml +++ b/docker/dev/tls/docker-compose.yml @@ -13,17 +13,17 @@ services: networks: external: aliases: - - traefik.${OPENPROJECT_DOCKER_DEV_TLD} - - openproject.${OPENPROJECT_DOCKER_DEV_TLD} - - openproject-assets.${OPENPROJECT_DOCKER_DEV_TLD} - - nextcloud.${OPENPROJECT_DOCKER_DEV_TLD} - - gitlab.${OPENPROJECT_DOCKER_DEV_TLD} - - keycloak.${OPENPROJECT_DOCKER_DEV_TLD} - - hocuspocus.${OPENPROJECT_DOCKER_DEV_TLD} - - minio.${OPENPROJECT_DOCKER_DEV_TLD} - - minioadmin.${OPENPROJECT_DOCKER_DEV_TLD} + - traefik.${OPENPROJECT_DOCKER_DEV_TLD:-local} + - openproject.${OPENPROJECT_DOCKER_DEV_TLD:-local} + - openproject-assets.${OPENPROJECT_DOCKER_DEV_TLD:-local} + - nextcloud.${OPENPROJECT_DOCKER_DEV_TLD:-local} + - gitlab.${OPENPROJECT_DOCKER_DEV_TLD:-local} + - keycloak.${OPENPROJECT_DOCKER_DEV_TLD:-local} + - hocuspocus.${OPENPROJECT_DOCKER_DEV_TLD:-local} + - minio.${OPENPROJECT_DOCKER_DEV_TLD:-local} + - minioadmin.${OPENPROJECT_DOCKER_DEV_TLD:-local} labels: - - "traefik.http.routers.traefik.rule=Host(`traefik.${OPENPROJECT_DOCKER_DEV_TLD}`)" + - "traefik.http.routers.traefik.rule=Host(`traefik.${OPENPROJECT_DOCKER_DEV_TLD:-local}`)" - "traefik.http.routers.traefik.service=api@internal" - "traefik.http.routers.traefik.entrypoints=websecure" From a141788ede588ef7070a8513317c45db867eab78 Mon Sep 17 00:00:00 2001 From: Eric Schubert Date: Fri, 30 Jan 2026 13:47:05 +0100 Subject: [PATCH 036/293] [chore] mention .env usage in readme --- .../development-environment/docker/README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/development/development-environment/docker/README.md b/docs/development/development-environment/docker/README.md index 18b9d22921b..5c7ccab5ba8 100644 --- a/docs/development/development-environment/docker/README.md +++ b/docs/development/development-environment/docker/README.md @@ -452,13 +452,14 @@ to have Nextcloud running to test the Nextcloud-OpenProject integration. To do t ### Alternative: Using Let's encrypt -An alternative approach is to issue certificates through Let's encrypt. This allows you to skip steps related to usage and setup -of a custom, non-trusted CA. However, it requires that you have access to a domain name that you control and requires additional -step to make the reverse proxy publicly reachable, which is not in scope of what this documentation can cover. +An alternative approach is to issue certificates through Let's encrypt. This allows you to skip steps related to usage +and setup of a custom, non-trusted CA. However, it requires that you have access to a domain name that you control and +requires an additional step to make the reverse proxy publicly reachable, which is not in the scope of what this +documentation can cover. -If you need such a setup, you can change the `docker-compose.override.yml` for the reverse proxy, to use `letsencrypt` (see the -corresponding `docker-compose.override.example.yml`). Make sure to export an environment variable with your alternative DNS zone -before starting anything via docker compose. For example: +If you need such a setup, you can change the `docker-compose.override.yml` for the reverse proxy, to use `letsencrypt` +(see the corresponding `docker-compose.override.example.yml`). Make sure to export an environment variable, or define +it in the `.env` files, with your alternative DNS zone before starting anything via docker compose. For example: ```bash export OPENPROJECT_DOCKER_DEV_TLD=dev.example.com From 68d851870db1d7da40a136d919a45e0ef6c991a6 Mon Sep 17 00:00:00 2001 From: ulferts Date: Fri, 30 Jan 2026 15:49:32 +0100 Subject: [PATCH 037/293] use appropriate font weight on the type --- .../admin/custom_fields/edit_form_header_component.html.erb | 2 +- .../project_custom_fields/edit_form_header_component.html.erb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/components/admin/custom_fields/edit_form_header_component.html.erb b/app/components/admin/custom_fields/edit_form_header_component.html.erb index eafe33fff11..477904b211d 100644 --- a/app/components/admin/custom_fields/edit_form_header_component.html.erb +++ b/app/components/admin/custom_fields/edit_form_header_component.html.erb @@ -31,7 +31,7 @@ See COPYRIGHT and LICENSE files for more details. render(Primer::OpenProject::PageHeader.new(test_selector: "custom-fields--page-header")) do |header| header.with_title { page_title } - header.with_breadcrumbs(breadcrumbs_items) + header.with_breadcrumbs(breadcrumbs_items, selected_item_font_weight: :normal) helpers.render_tab_header_nav(header, tabs, test_selector: :custom_field_detail_header) end diff --git a/app/components/settings/project_custom_fields/edit_form_header_component.html.erb b/app/components/settings/project_custom_fields/edit_form_header_component.html.erb index 818d7d61e17..ac46b0e0433 100644 --- a/app/components/settings/project_custom_fields/edit_form_header_component.html.erb +++ b/app/components/settings/project_custom_fields/edit_form_header_component.html.erb @@ -30,7 +30,7 @@ See COPYRIGHT and LICENSE files for more details. render(Primer::OpenProject::PageHeader.new) do |header| header.with_title { page_title } header.with_description { t("settings.project_attributes.edit.description") } unless hide_description? - header.with_breadcrumbs(breadcrumbs_items) + header.with_breadcrumbs(breadcrumbs_items, selected_item_font_weight: :normal) helpers.render_tab_header_nav(header, tabs, test_selector: :project_attribute_detail_header) end From 4a5fd2c05f76727afdc8afc3e347138db0907500 Mon Sep 17 00:00:00 2001 From: OpenProject Actions CI Date: Sat, 31 Jan 2026 03:46:24 +0000 Subject: [PATCH 038/293] update locales from crowdin [ci skip] --- config/locales/crowdin/cs.yml | 50 +- config/locales/crowdin/de.yml | 28 +- config/locales/crowdin/es.yml | 2 +- config/locales/crowdin/ja.yml | 107 ++-- config/locales/crowdin/js-ca.yml | 2 +- config/locales/crowdin/js-de.yml | 8 +- config/locales/crowdin/js-ja.yml | 82 +-- config/locales/crowdin/js-no.yml | 2 +- config/locales/crowdin/js-ro.yml | 2 +- config/locales/crowdin/js-ru.yml | 2 +- config/locales/crowdin/ro.yml | 4 +- config/locales/crowdin/sl.yml | 4 +- config/locales/crowdin/uk.yml | 2 +- config/locales/crowdin/zh-CN.seeders.yml | 4 +- config/locales/crowdin/zh-CN.yml | 12 +- config/locales/crowdin/zh-TW.yml | 10 +- .../backlogs/config/locales/crowdin/ro.yml | 2 +- .../backlogs/config/locales/crowdin/zh-TW.yml | 2 +- .../bim/config/locales/crowdin/da.seeders.yml | 598 +++++++++--------- modules/bim/config/locales/crowdin/da.yml | 174 ++--- modules/bim/config/locales/crowdin/fr.yml | 2 +- modules/budgets/config/locales/crowdin/cs.yml | 2 +- modules/costs/config/locales/crowdin/ja.yml | 2 +- .../config/locales/crowdin/da.yml | 4 +- .../config/locales/crowdin/rw.yml | 27 +- .../config/locales/crowdin/uz.yml | 27 +- .../config/locales/crowdin/zh-CN.yml | 2 +- .../config/locales/crowdin/zh-TW.yml | 2 +- modules/meeting/config/locales/crowdin/cs.yml | 2 +- modules/meeting/config/locales/crowdin/ja.yml | 14 +- .../reporting/config/locales/crowdin/ro.yml | 2 +- .../config/locales/crowdin/zh-TW.yml | 4 +- .../storages/config/locales/crowdin/ja.yml | 204 +++--- .../storages/config/locales/crowdin/js-ja.yml | 18 +- .../config/locales/crowdin/js-fr.yml | 2 +- .../config/locales/crowdin/ro.yml | 2 +- .../config/locales/crowdin/ru.yml | 2 +- .../config/locales/crowdin/uk.yml | 2 +- .../config/locales/crowdin/zh-CN.yml | 2 +- 39 files changed, 719 insertions(+), 700 deletions(-) diff --git a/config/locales/crowdin/cs.yml b/config/locales/crowdin/cs.yml index 15eb4782fae..1b9760de5ee 100644 --- a/config/locales/crowdin/cs.yml +++ b/config/locales/crowdin/cs.yml @@ -1270,7 +1270,7 @@ cs: enabled_modules: "Povolené moduly" identifier: "Identifikátor" latest_activity_at: "Poslední aktivita" - parent: "Nadřazený projekt" + parent: "Podprojekt" project_creation_wizard_enabled: "Project initiation request" public_value: title: "Viditelnost" @@ -1639,7 +1639,7 @@ cs: meeting: error_conflict: "Nelze uložit, protože schůzku mezitím aktualizoval někdo jiný. Znovu načtěte stránku." notifications: - at_least_one_channel: "Pro odesílání notifikací musí být specifikován alespoň jeden kanál" + at_least_one_channel: "Alespoň jeden kanál pro odesílání oznámení musí být specifikován." attributes: read_ian: read_on_creation: "nelze nastavit na pravdivé při vytváření oznámení " @@ -1937,11 +1937,11 @@ cs: member: "Člen" news: "Novinky" notification: - one: "Notifikace" - few: "Notifikací" - many: "Notifikací" - other: "Notifikace" - placeholder_user: "Placeholder uživatel" + one: "Oznámení" + few: "Oznámení" + many: "Oznámení" + other: "Oznámení" + placeholder_user: "placeholder uživatel" project: one: "Projekt" few: "Projekty" @@ -3007,7 +3007,7 @@ cs: instructions_after_error: "Zkuste se znovu přihlásit kliknutím na %{signin}. Pokud chyba přetrvává, požádejte správce o pomoc." menus: admin: - mail_notification: "E-mailové notifikace" + mail_notification: "E-mailová upozornění" mails_and_notifications: "E-maily a oznámení" aggregation: "Agregace" api_and_webhooks: "API & Webhooky" @@ -3094,7 +3094,7 @@ cs: by_project: "Nepřečteno dle projektu" by_reason: "Důvod" inbox: "Doručená pošta" - send_notifications: "Pro tuto akci odeslat notifikaci" + send_notifications: "Odeslat oznámení pro tuto akci" work_packages: subject: created: "Pracovní balíček byl vytvořen." @@ -3536,9 +3536,9 @@ cs: label_permissions: "Práva" label_permissions_report: "Přehled oprávnění" label_personalize_page: "Přizpůsobit tuto stránku" - label_placeholder_user: "Placeholder uživatel" + label_placeholder_user: "placeholder uživatel" label_placeholder_user_new: "" - label_placeholder_user_plural: "Placeholder uživatelé" + label_placeholder_user_plural: "placeholder uživatelé" label_planning: "Plánování" label_please_login: "Přihlaste se prosím" label_plugins: "Pluginy" @@ -3562,7 +3562,7 @@ cs: label_project_attribute_plural: "Atributy projektu" label_project_attribute_manage_link: "Správa atributů produktu" label_project_count: "Celkový počet projektů" - label_project_copy_notifications: "Během kopírování projektu odeslat notifikace e-mailem" + label_project_copy_notifications: "Během kopie projektu odeslat oznámení e-mailem" label_project_initiation_export_pdf: "Export PDF for %{project_creation_name}" label_project_latest: "Nejnovější projekty" label_project_default_type: "Povolit prázdný typ" @@ -3727,7 +3727,7 @@ cs: label_version_new: "Nová verze" label_version_edit: "Upravit verzi" label_version_plural: "Verze" - label_version_sharing_descendants: "S podprojekty" + label_version_sharing_descendants: "S Podprojekty" label_version_sharing_hierarchy: "S hierarchií projektu" label_version_sharing_none: "Není sdíleno" label_version_sharing_system: "Se všemi projekty" @@ -3835,28 +3835,28 @@ cs: digests: including_mention_singular: "včetně zmínky" including_mention_plural: "včetně %{number_mentioned} zmínění" - unread_notification_singular: "1 nepřečtená notifikace" - unread_notification_plural: "%{number_unread} nepřečtených notifikací" + unread_notification_singular: "1 nepřečtené oznámení" + unread_notification_plural: "%{number_unread} nepřečtených oznámení" you_have: "Máte" logo_alt_text: "Logo" mention: subject: "%{user_name} vás zmínil v #%{id} - %{subject}" notification: - center: "Centrum notifikací" + center: "Centrum oznámení" see_in_center: "Zobrazit komentář v oznamovacím centru" settings: "Změnit nastavení e-mailu" salutation: "Ahoj %{user}!" salutation_full_name: "Jméno a příjmení" work_packages: created_at: "Vytvořeno v %{timestamp} uživatelem %{user} " - login_to_see_all: "Přihlaste se pro zobrazení všech notifikací." + login_to_see_all: "Přihlaste se pro zobrazení všech oznámení." mentioned: "Byli jste zmíněni v komentáři" mentioned_by: "%{user} vás zmínil v komentáři OpenProject" more_to_see: - one: "Existuje ještě 1 pracovní balíček s notifikací." - few: "Existuje ještě %{count} pracovních balíčků s notifikacema." - many: "Existuje ještě %{count} pracovních balíčků s notifikacema." - other: "Existuje ještě %{count} pracovních balíčků s notifikacema." + one: "Máte ještě 1 pracovní balíček s notifikací." + few: "Existuje ještě %{count} pracovních balíčků s oznámeními." + many: "Máte ještě %{count} pracovních balíčků s notifikacemi." + other: "Existuje ještě %{count} pracovních balíčků s oznámeními." open_in_browser: "Otevřít v prohlížeči" reason: watched: "Sledováno" @@ -3865,7 +3865,7 @@ cs: mentioned: "Zmíněné" shared: "Sdílené" subscribed: "vše" - prefix: "Obdrženo z důvodu nastavení notifikací: %{reason}" + prefix: "Obdrženo z důvodu nastavení oznámení: %{reason}" date_alert_start_date: "Upozornění na datum" date_alert_due_date: "Upozornění na datum" reminder: "Připomínka" @@ -4164,7 +4164,7 @@ cs: permission_move_work_packages: "Přesun pracovních balíčků" permission_protect_wiki_pages: "Ochrana stránky wiki" permission_rename_wiki_pages: "Přejmenovat stránky wiki" - permission_save_queries: "Uložit zobrazení" + permission_save_queries: "Uložit pohled" permission_search_project: "Hledat projekt" permission_select_custom_fields: "Vybrat vlastní pole" permission_select_project_custom_fields: "Vyberte atributy projektu" @@ -4645,7 +4645,7 @@ cs: enable_subscriptions_text_html: Umožňuje uživatelům s nezbytnými oprávněními přihlásit se do OpenProject kalendářů a získat přístup k informacím o pracovním balíčku prostřednictvím externího klienta kalendáře. Poznámka: Před povolením si prosím přečtěte podrobnosti o odběru. language_name_being_default: "%{language_name} (výchozí)" notifications: - events_explanation: "Určuje, pro kterou událost je odeslán e-mail. Pracovní balíčky jsou z tohoto seznamu vyloučeny, protože notifikace pro ně mohou být nastavena speciálně pro každého uživatele." + events_explanation: "Určuje, pro kterou událost je odeslán e-mail. Pracovní balíčky jsou z tohoto seznamu vyloučeny, protože oznámení pro ně mohou být nastavena speciálně pro každého uživatele." delay_minutes_explanation: "Odesílání e-mailu může být pozdrženo, aby bylo uživatelům s nakonfigurovaným v oznámení aplikace před odesláním pošty potvrzeno oznámení. Uživatelé, kteří si přečtou oznámení v aplikaci, nedostanou e-mail pro již přečtené oznámení." other: "Ostatní" passwords: "Hesla" @@ -4826,7 +4826,7 @@ cs: text_destroy_with_associated: "Existují další objekty, které jsou přiřazeny k pracovním balíčkům a které mají být odstraněny. Tyto objekty jsou následující typy:" text_destroy_what_to_do: "Co chcete udělat?" text_diff_truncated: "... Toto rozlišení bylo zkráceno, protože přesahuje maximální velikost, kterou lze zobrazit." - text_email_delivery_not_configured: "Doručení e-mailu není nakonfigurováno a notifikace jsou zakázány.\nNakonfigurujte váš SMTP server pro jejich povolení." + text_email_delivery_not_configured: "Doručení e-mailu není nakonfigurováno a oznámení jsou zakázána.\nNakonfigurujte váš SMTP server pro jejich povolení." text_enumeration_category_reassign_to: "Přiřadit je k této hodnotě:" text_enumeration_destroy_question: "%{count} objektů je přiřazeno k této hodnotě." text_file_repository_writable: "Do adresáře příloh lze zapisovat" diff --git a/config/locales/crowdin/de.yml b/config/locales/crowdin/de.yml index 3b206b45135..b37a0a9d6a6 100644 --- a/config/locales/crowdin/de.yml +++ b/config/locales/crowdin/de.yml @@ -85,11 +85,11 @@ de: title: "Enterprise-Token hinzufügen" type_token_text: "Enterprise-Token Text" token_placeholder: "Enterprise-Token Text hier einfügen" - add_token: "Enterprise-Edition Support Token hochladen" + add_token: "Enterprise edition Support Token hochladen" replace_token: "Aktuellen Enterprise edition Support Token ersetzen" order: "Enterprise on-premises bestellen" - paste: "Enterprise-Edition Support Token hier einfügen" - required_for_feature: "Dieses Add-on ist nur mit einem aktiven Enterprise-Edition Support-Token verfügbar." + paste: "Enterprise edition Support Token hier einfügen" + required_for_feature: "Dieses Add-on ist nur mit einem aktiven Enterprise edition Support-Token verfügbar." enterprise_link: "Klicken Sie hier für weitere Informationen." start_trial: "Kostenlose Testversion starten" book_now: "Jetzt buchen" @@ -899,10 +899,10 @@ de: tab: "Titel konfigurieren" manually_editable_subjects: label: "Manuell bearbeitbare Titel" - caption: "Nutzer:innen können die Titel der Arbeitspakete ohne Einschränkungen manuell eingeben und bearbeiten." + caption: "Benutzer können die Titel der Arbeitspakete ohne Einschränkungen manuell eingeben und bearbeiten." automatically_generated_subjects: label: "Automatisch generierte Titel" - caption: "Definieren Sie ein Schema aus referenzierten Attributen und Freitext für die automatische Generierung von Arbeitspakettiteln. Nutzer:innen können diese nicht manuell editieren." + caption: "Definieren Sie ein Schema aus referenzierten Attributen und Freitext für die automatische Generierung von Arbeitspakettiteln. Nutzer können diese nicht manuell editieren." token: label_with_context: "%{attribute_context}: %{attribute_label}" context: @@ -956,7 +956,7 @@ de: manual_with_children: "Hat Unteraufgaben aber ihre Startdaten werden ignoriert." title: automatic_mobile: "Automatisch geplant." - automatic_with_children: "Unteraufgaben bestimmen Termine." + automatic_with_children: "Die Termine sind durch untergeordnete Arbeitspakete bestimmt." automatic_with_predecessor: "Der Anfangstermin wird von einem Vorgänger festgelegt." manual_mobile: "Manuell geplant." manually_scheduled: "Manuell geplant – Daten unabhängig von Beziehungen." @@ -1059,7 +1059,7 @@ de: label_child_plural: "Unteraufgaben" new_child: "Neue Unteraufgabe" new_child_description: "Erstellt ein zugehöriges Arbeitspaket als Unteraufgabe des aktuellen (übergeordneten) Arbeitspakets" - child: "Unteraufgabe" + child: "Kind" child_description: "Macht das zugehörige Arbeitspaket zu einer Unteraufgabe des aktuellen (übergeordneten) Arbeitspakets" parent: "Übergeordnetes Arbeitspaket" parent_description: "Wandelt das verknüpfte in ein übergeordnetes Arbeitspaket dieses Arbeitspakets um" @@ -1299,7 +1299,7 @@ de: column_names: "Spalten" relations_to_type_column: "Beziehungen zu %{type}" relations_of_type_column: "Beziehungen der Art: %{type}" - child_work_packages: "Unteraufgaben" + child_work_packages: "Kinder" group_by: "Gruppiere Ergebnisse nach" sort_by: "Ergebnisse sortieren nach" filters: "Filter" @@ -1796,7 +1796,7 @@ de: status_transition_invalid: "ist ungültig, da kein valider Übergang vom alten zum neuen Status für die aktuelle Rolle des Nutzers existiert." status_invalid_in_type: "ist ungültig, da der aktuelle Status nicht in diesem Typ vorhanden ist." type: - cannot_be_milestone_due_to_children: "kann kein Meilenstein werden, da dieses Arbeitspaket Unteraufgaben besitzt." + cannot_be_milestone_due_to_children: "kann kein Meilenstein werden, da dieses Arbeitspaket Unterelemente besitzt." priority_id: only_active_priorities_allowed: "muss aktiv sein." category: @@ -2597,7 +2597,7 @@ de: error_custom_option_not_found: "Option ist nicht vorhanden." error_enterprise_plan_needed: "Sie benötigen den Enterprise-Plan %{plan}, um diese Aktion durchzuführen." error_enterprise_activation_user_limit: "Ihr Konto konnte nicht aktiviert werden (Nutzerlimit erreicht). Bitte kontaktieren Sie Ihren Administrator um Zugriff zu erhalten." - error_enterprise_token_invalid_domain: "Die Enterprise-Edition ist nicht aktiv. Die aktuelle Domain (%{actual}) entspricht nicht dem erwarteten Hostnamen (%{expected})." + error_enterprise_token_invalid_domain: "Die Enterprise edition ist nicht aktiv. Die aktuelle Domain (%{actual}) entspricht nicht dem erwarteten Hostnamen (%{expected})." error_failed_to_delete_entry: "Fehler beim Löschen dieses Eintrags." error_in_dependent: "Fehler beim Versuch, abhängiges Objekt zu ändern: %{dependent_class} #%{related_id} - %{related_subject}: %{error}" error_in_new_dependent: "Fehler beim Versuch, abhängiges Objekt zu erstellen: %{dependent_class} - %{related_subject}: %{error}" @@ -2874,7 +2874,7 @@ de: dates: working: "%{date} ist jetzt ein Arbeitstag" non_working: "%{date} ist jetzt ein arbeitsfreier Tag" - progress_mode_changed_to_status_based: Fortschrittberechnung wurde auf Status-bezogen gesetzt + progress_mode_changed_to_status_based: Fortschrittberechnung wurde auf Status-basiert gesetzt status_excluded_from_totals_set_to_false_message: jetzt in den Gesamtwerten der Hierarchie enthalten status_excluded_from_totals_set_to_true_message: jetzt von den Hierarchie-Gesamtwerten ausgeschlossen status_percent_complete_changed: "% abgeschlossen von %{old_value}% auf %{new_value} % geändert" @@ -3195,7 +3195,7 @@ de: label_enumerations: "Aufzählungen" label_enterprise: "Enterprise" label_enterprise_active_users: "%{current}/%{limit} gebuchte aktive Nutzer" - label_enterprise_edition: "Enterprise Edition" + label_enterprise_edition: "Enterprise edition" label_enterprise_support: "Enterprise Support" label_environment: "Umgebung" label_estimates_and_progress: "Schätzungen und Fortschritt" @@ -4251,7 +4251,7 @@ de: update_timeout: "Speichere die Informationen bzgl. des genutzten Festplattenspeichers eines Projektarchivs für N Minuten.\nErhöhen Sie diesen Wert zur Verbesserung der Performance, da die Erfassung des genutzten Festplattenspeichers Ressourcen-intensiv ist." oauth_application_details: "Der Client Geheimcode wird nach dem Schließen dieses Fensters nicht mehr zugänglich sein. Bitte kopieren Sie diese Werte in die Nextcloud OpenProject Integrationseinstellungen:" oauth_application_details_link_text: "Zu den Einstellungen gehen" - setup_documentation_details: "Wenn Sie Hilfe bei der Konfiguration eines neuen Dateispeichers benötigen, konsultieren Sie bitte die Dokumentation: " + setup_documentation_details: "Wenn Sie Hilfe bei der Konfiguration eines neuen Datei-Speichers benötigen, konsultieren Sie bitte die Dokumentation: " setup_documentation_details_link_text: "Dateispeicher einrichten" show_warning_details: "Um diesen Dateispeicher nutzen zu können, müssen Sie das Modul und den spezifischen Speicher in den Projekteinstellungen jedes gewünschten Projekts aktivieren." subversion: @@ -4887,7 +4887,7 @@ de: warning_user_limit_reached_admin: > Das Hinzufügen zusätzlicher Benutzer überschreitet das aktuelle Benutzerlimit. Bitte aktualisieren Sie Ihr Abonnement um sicherzustellen, dass externe Benutzer auf diese Instanz zugreifen können. warning_user_limit_reached_instructions: > - Du hast dein Nutzerlimit erreicht (%{current}/%{max} active users). Bitte kontaktiere sales@openproject.com um deinen Enterprise Edition Plan upzugraden und weitere Nutzer hinzuzufügen. + Du hast dein Nutzerlimit erreicht (%{current}/%{max} active users). Bitte kontaktiere sales@openproject.com um deinen Enterprise edition Plan upzugraden und weitere Nutzer hinzuzufügen. warning_protocol_mismatch_html: > warning_bar: diff --git a/config/locales/crowdin/es.yml b/config/locales/crowdin/es.yml index 9f03a2c370d..30b2acdc079 100644 --- a/config/locales/crowdin/es.yml +++ b/config/locales/crowdin/es.yml @@ -964,7 +964,7 @@ es: automatic_with_children: "Fechas determinadas por paquetes de trabajo secundarios." automatic_with_predecessor: "La fecha de inicio la fija un predecesor." manual_mobile: "Programado manualmente." - manually_scheduled: "Programado manualmente. No afectadas por relaciones." + manually_scheduled: "Programado manualmente. Fechas no afectadas por relaciones." blankslate: title: "Sin predecesores" description: "Para activar la programación automática, este paquete de trabajo debe tener al menos un predecesor. Entonces se programará automáticamente para que comience después del predecesor más cercano." diff --git a/config/locales/crowdin/ja.yml b/config/locales/crowdin/ja.yml index 6b7d10277ab..bad61bb62b4 100644 --- a/config/locales/crowdin/ja.yml +++ b/config/locales/crowdin/ja.yml @@ -107,17 +107,17 @@ ja: jemalloc_allocator: Jemalloc メモリアロケータ journal_aggregation: explanation: - text: "ユーザーの個々のアクション(例えば、ワークパッケージを2回更新する)は、それらの年齢差が指定されたタイムスパン未満である場合、単一のアクションに集約されます。これらはアプリケーション内で1つのアクションとして表示されます。これはまた、送信されるメールの数を減らし、 %{webhook_link} の遅延にも影響します。" + text: "ユーザーの個々のアクション (例:ワークパッケージを2回更新する)は、指定された時間範囲よりも時間差が小さい場合、単一のアクションに集約されます。 これらはアプリケーション内で単一のアクションとして表示されます。 これにより、送信されるメールの数が減少し、 %{webhook_link} の遅延にも影響します。" link: "webhook" scim_clients: authentication_methods: - sso: "IDプロバイダーからのJWT" - oauth2_client: "OAuth 2.0クライアント認証情報" + sso: "アイデンティティプロバイダからのJWT" + oauth2_client: "OAuth 2.0 クライアント資格情報" oauth2_token: "静的アクセストークン" created_client_credentials_dialog_component: - title: "クライアント認証情報の作成" - heading: "クライアント認証情報が生成されました" - one_time_hint: "クライアント・シークレットが表示されるのはこの時だけです。必ずコピーしてください。" + title: "クライアントの資格情報が作成されました" + heading: "クライアントの資格情報が生成されました" + one_time_hint: "クライアントのシークレットが表示される唯一の時間です。今すぐコピーしてください。" created_token_dialog_component: title: "トークンを作成しました" heading: "トークンが生成されました" @@ -130,21 +130,21 @@ ja: edit: label_delete_scim_client: "SCIM クライアントを削除" form: - auth_provider_description: "これは、SCIM プロバイダによって追加されたユーザが OpenProject で認証するために使用するサービスです。" - authentication_method_description_html: "これは SCIM クライアントが OpenProject で認証する方法です。OAuth トークンにscim_v2スコープが含まれていることを確認してください。" - description: "これらの設定オプションの詳細については、[SCIMクライアントの設定に関する文書](docs_url)を参照してください。" + auth_provider_description: "これは、SCIMプロバイダが追加したユーザーがOpenProjectでの認証に使用するサービスです。" + authentication_method_description_html: "これは SCIM クライアントが OpenProject で認証する方法です。OAuth トークンに scim_v2 スコープが含まれていることを確認してください。" + description: "設定オプションの詳細については、[SCIM クライアントの設定に関するドキュメント](docs_url)を参照してください。" jwt_sub_description: "例えば、Keycloakの場合、これはSCIMクライアントに関連付けられたサービスアカウントのUUIDです。あなたのユースケースにあった Subject claim を見つける方法については [ドキュメント](docs_url) を参照してください。" - name_description: "このクライアントが設定された理由を他の管理者が理解しやすい名前を選んでください。" + name_description: "他の管理者がこのクライアントが設定された理由を理解するのに役立つ名前を選択してください。" index: - description: "ここで設定された SCIM クライアントは、OpenProject SCIM サーバ API と対話し、ユーザアカウントやグループのプロビジョニング、更新、デプロビジョニングを行うことができます。" - label_create_button: "SCIMクライアントの追加" + description: "ここで設定されたSCIMクライアントは、OpenProjectのSCIMサーバー APIと相互作用して、ユーザーアカウントとグループのプロビジョニング、更新、およびデプロビジョニングを行うことができます。" + label_create_button: "SCIMクライアントを追加" new: title: "新しいSCIMクライアント" revoke_static_token_dialog_component: confirm_button: "取り消す" - title: "静的トークンの失効" - heading: "このトークンを本当に取り消しますか?" - description: "このトークンを使っている SCIM クライアントは、OpenProject の SCIM サーバ API にアクセスできなくなります。" + title: "静的トークンを取り消す" + heading: "このトークンを取り消してもよろしいですか?" + description: "このトークンを使用する SCIM クライアントは、OpenProject の SCIM サーバ API にアクセスできなくなります。" table_component: blank_slate: title: "SCIMクライアントがまだ設定されていません" @@ -674,26 +674,26 @@ ja: other: "また、 %{shared_work_packages_link} はこのユーザーと共有されています。" remove_project_membership_or_work_package_shares_too: "直接のメンバーとしてのユーザーだけを削除したい(および共有を維持したい)、またはワークパッケージの共有も削除しますか?" will_remove_all_user_access_priveleges: "このメンバーを削除すると、プロジェクトへのユーザーのすべてのアクセス権が削除されます。ユーザーはまだサイトの一部として存在します。" - will_remove_all_group_access_priveleges: "このメンバを削除すると、プロジェクトに対するグループのすべてのアクセス権が削除されます。グループはサイトの一部としてまだ存在します。" - cannot_delete_inherited_membership: "このプロジェクトのメンバーであるグループに所属しているため、このメンバーを削除することはできません。" - cannot_delete_inherited_membership_note_admin_html: "%{administration_settings_link}で、プロジェクトのメンバーとしてグループを削除することも、特定のメンバーをグループから削除することもできます。" - cannot_delete_inherited_membership_note_non_admin: "プロジェクトのメンバーとしてグループを削除するか、管理者に連絡してこの特定のメンバーをグループから削除することができます。" + will_remove_all_group_access_priveleges: "このメンバーを削除すると、グループのすべてのアクセス権がプロジェクトに削除されます。グループはサイトの一部として存在します。" + cannot_delete_inherited_membership: "このメンバーはこのプロジェクトのメンバーであるグループに属しているため、削除できません。" + cannot_delete_inherited_membership_note_admin_html: "プロジェクトのメンバーとしてグループを削除するか、 %{administration_settings_link} のグループからこの特定のメンバーを削除することができます。" + cannot_delete_inherited_membership_note_non_admin: "プロジェクトのメンバーとしてグループを削除するか、管理者に問い合わせてグループから特定のメンバーを削除することができます。" delete_work_package_shares_dialog: - title: "ワーク・パッケージ・シェアの破棄" + title: "ワークパッケージの共有の取り消し" shared_with_this_user_html: other: "%{all_shared_work_packages_link} はこのユーザーと共有されています。" shared_with_this_group_html: other: "%{all_shared_work_packages_link} はこのグループと共有されています。" shared_with_permission_html: other: "%{shared_work_packages_link} のみが %{shared_role_name} 権限と共有されています。" - revoke_all_or_with_role: "すべての共有ワークパッケージ、または %{shared_role_name} 権限を持つワークパッケージのみへのアクセス権を剥奪しますか?" - will_not_affect_inherited_shares: "(これは、そのグループと共有しているワークパッケージには影響しません)。" - cannot_remove_inherited: "グループで共有されたワークパッケージの共有は削除できません。" - cannot_remove_inherited_with_role: "ロール %{shared_role_name} で共有されるワークパッケージは、グループを介して共有され、削除することはできません。" - cannot_remove_inherited_note_admin_html: "%{administration_settings_link}、グループへの共有を取り消すか、グループからこの特定のメンバーを削除することができます。" - cannot_remove_inherited_note_non_admin: "グループへの共有を取り消すか、管理者に連絡して特定のメンバーをグループから削除することができます。" - will_revoke_directly_granted_access: "このアクションは、グループと共有されているワークパッケージ以外の、すべてのワークパッケージへのアクセス権を剥奪する。" - will_revoke_access_to_all: "このアクションは、すべてのアクセス権を剥奪する。" + revoke_all_or_with_role: "共有されたワークパッケージ、または %{shared_role_name} 権限を持つワークパッケージのみへのアクセスを取り消しますか?" + will_not_affect_inherited_shares: "(これはグループと共有されているワークパッケージには影響しません)。" + cannot_remove_inherited: "グループ間で共有されるワークパッケージは削除できません。" + cannot_remove_inherited_with_role: "ワークパッケージとロール %{shared_role_name} が共有されているため、削除できません。" + cannot_remove_inherited_note_admin_html: "あなたは、グループへの共有を取り消すか、 %{administration_settings_link} のグループからこの特定のメンバーを削除することができます。" + cannot_remove_inherited_note_non_admin: "共有をグループに取り消すか、管理者に問い合わせてグループから特定のメンバーを削除することができます。" + will_revoke_directly_granted_access: "このアクションは、すべてのユーザーへのアクセスを取り消しますが、グループと共有されているワークパッケージです。" + will_revoke_access_to_all: "このアクションは、すべてのユーザーへのアクセスを取り消します。" my: access_token: dialog: @@ -717,7 +717,7 @@ ja: no_results_title_text: "現在、有効なアクセス トークンはありません。" notice_api_token_revoked: "APIトークンが削除されました。新しいトークンを作成するには、APIセクションの作成ボタンを使用してください。" notice_rss_token_revoked: "RSSトークンが削除されました。新しいトークンを作成するには、RSSセクションのリンクを使用してください。" - notice_ical_token_revoked: 'プロジェクト "%{project_name}" のカレンダー "%{calendar_name}" の iCalendar トークン "%{token_name}" が失効しました。このトークンを持つiCalendar URLは無効になりました。' + notice_ical_token_revoked: 'プロジェクト "%{token_name}" のカレンダー "%{calendar_name}" の iCalendar トークン "%{project_name}" が取り消されました。 このトークンのiCalendar URLは無効です。' password_confirmation_dialog: confirmation_required: "You need to enter your account password to confirm this change." title: "Confirm your password to continue" @@ -738,7 +738,7 @@ ja: matrix_check_uncheck_all_in_col_label_html: "Toggle all %{module} permissions for %{role} role" users: autologins: - prompt: "ログインしたまま %{num_days}" + prompt: "%{num_days} のログインを維持" sessions: session_name: "%{browser_name} %{browser_version} の %{os_name}" browser: "ブラウザ" @@ -752,17 +752,17 @@ ja: current: "Current (this device)" title: "セッション管理" instructions: "You are logged in to your account through the following devices. Revoke sessions that you do not recognise or from devices you do not control." - may_not_delete_current: "現在のセッションを削除することはできません。" + may_not_delete_current: "現在のセッションは削除できません。" deletion_warning: "Are you sure you want to revoke this session? You will be logged out on this device." groups: member_in_these_groups: "このユーザーは現在以下のグループのメンバーです:" no_results_title_text: このユーザーは現在どのグループのメンバーでもありません。 - summary_with_more: '%{names} と %{count_link}のメンバー。' - more: "%{count} もっと見る" - summary: '%{names}のメンバー。' + summary_with_more: '%{names} と %{count_link} のメンバー。' + more: "%{count} 以上" + summary: '%{names} のメンバー .' memberships: no_results_title_text: このユーザは現在プロジェクトのメンバーではありません。 - open_profile: "プロフィール" + open_profile: "プロファイルを開く" invite_user_modal: invite: "招待" title: @@ -819,7 +819,7 @@ ja: placeholder_users: right_to_manage_members_missing: > プレースホルダーユーザを削除する権限がありません。 プレースホルダー ユーザーがメンバーであるすべてのプロジェクトのメンバーを管理する権利はありません。 - delete_tooltip: "プレースホルダー・ユーザーの削除" + delete_tooltip: "プレースホルダー ユーザーを削除" deletion_info: heading: "プレースホルダー ユーザー %{name} を削除" data_consequences: > @@ -837,11 +837,11 @@ ja: reactions: action_title: "リアクト" add_reaction: "リアクションを追加" - react_with: "%{reaction} と リアクト" - and_user: "および %{user}" + react_with: "%{reaction} で反応する" + and_user: "と %{user}" and_others: other: と %{count} その他 - reaction_by: "%{reaction} によって" + reaction_by: "%{reaction} による" reportings: index: no_results_title_text: 現在、ステータス報告はありません。 @@ -852,19 +852,20 @@ ja: このステータスの色を割り当てたり変更する場合にクリックします。 ステータスボタンに表示され、テーブル内のワークパッケージを強調表示するために使用できます。 status_default_text: |- - 新しいワークパッケージは、デフォルトでこのタイプに設定される。読み取り専用にはできない。 + 新しいワークパッケージはデフォルトでこのタイプに設定されています。読み取り専用にすることはできません。 status_excluded_from_totals_text: |- - このステータスを持つワークパッケージを、階層内の「作業」、「 - 残作業」、「完了率」の合計から除外するには、このオプションをオンにします。 + このオプションをオンにすると、このステータスのワークパッケージを合計作業量、 + 残作業量、および階層構造で完了させることができます。 status_percent_complete_text: |- ステータスベースの進捗計算モードでは、このステータスが選択されると、作業 パッケージの「完了%」が自動的にこの値に設定される。 ワークベースモードでは無視される。 status_readonly_html: | - このステータスを持つワークパッケージを読み取り専用としてマークするには、このオプションをチェックする。 - ステータス以外の属性は変更できません。 + ワークパッケージを読み取り専用としてマークするには、このオプションをオンにしてください。 + ステータスを除いて変更することはできません。 +
- 注意: 継承された値 (子やリレーションなど) は適用されます。 + メモ: 継承された値 (例えば、子や関連) が適用されます。 index: no_results_title_text: 現在、ワークパッケージのステータスはありません。 no_results_content_text: 新しいステータスを追加 @@ -874,7 +875,7 @@ ja: is_readonly: "読み取り専用" excluded_from_totals: "合計から除外" themes: - dark: "暗い" + dark: "ダーク" light: "ライト" sync_with_os: "自動(OSのテーマ設定に追従)" types: @@ -992,15 +993,15 @@ ja: could_not_be_saved: "次のワークパッケージを保存できませんでした:" none_could_be_saved: "%{total} 作業パッケージのどれも更新できませんでした。" x_out_of_y_could_be_saved: "%{failing} の %{total} ワークパッケージのうち、 %{success} を更新できませんでした。" - selected_because_descendants: "%{selected} のワークパッケージが選択されたが、合計 %{total} のワークパッケージが影響を受け、その中には子孫も含まれる。" - descendant: "選択された子孫" + selected_because_descendants: "%{selected} ワークパッケージが選択されている間、合計で %{total} ワークパッケージが子孫を含む影響を受けます。" + descendant: "選択された子孫です" move: no_common_statuses_exists: "選択されたすべてのワークパッケージに利用できるステータスはありません。 それらの状態は変更できません。" unsupported_for_multiple_projects: "複数のプロジェクトからのワークパッケージの一括移動 / コピーはサポートされていません" current_type_not_available_in_target_project: > - ワークパッケージの現在のタイプがターゲットプロジェクトで有効になっていません。変更しない場合は、ターゲットプロジェクトでタイプを有効にしてください。そうでない場合は、リストからターゲットプロジェクトで使用可能なタイプを選択してください。 + ターゲット プロジェクトで現在のワークパッケージのタイプが有効になっていません。 変更を行わないようにしたい場合は、対象プロジェクトのタイプを有効にしてください。 それ以外の場合は、リストからターゲット プロジェクトで使用可能なタイプを選択します。 bulk_current_type_not_available_in_target_project: > - ワークパッケージの現在のタイプがターゲットプロジェクトで有効になっていません。変更しない場合は、ターゲットプロジェクトでタイプを有効にしてください。そうでない場合は、リストからターゲットプロジェクトで使用可能なタイプを選択してください。 + 現在のタイプのワークパッケージはターゲット プロジェクトで有効になっていません。 変更を行わないようにしたい場合は、対象プロジェクトのタイプを有効にしてください。 それ以外の場合は、リストからターゲット プロジェクトで使用可能なタイプを選択します。 sharing: missing_workflow_warning: title: "ワークパッケージの共有のためのワークフローがありません" @@ -1025,9 +1026,9 @@ ja: no_results_title_text: 現在、有効なバージョンはありません。 work_package_relations_tab: index: - action_bar_title: "他のワークパッケージとのリレーションを追加して、それらの間にリンクを作成する。" - no_results_title_text: 現在、利用可能な関係はない。 - blankslate_heading: "関係なし" + action_bar_title: "他のワークパッケージにリレーションを追加して、その間にリンクを作成します。" + no_results_title_text: 現在利用可能なリレーションはありません。 + blankslate_heading: "リレーションなし" blankslate_description: "このワークパッケージにはまだリレーションがありません。" label_add_child_button: "子要素" label_add_x: "%{x} を追加" diff --git a/config/locales/crowdin/js-ca.yml b/config/locales/crowdin/js-ca.yml index b4773fd1995..1ad220d568b 100644 --- a/config/locales/crowdin/js-ca.yml +++ b/config/locales/crowdin/js-ca.yml @@ -104,7 +104,7 @@ ca: button_save: "Desa" button_settings: "Configuració" button_uncheck_all: "Desmarca-ho tot" - button_update: "Actualitzar" + button_update: "Actualitza" button_export-atom: "Descarregar Atom" button_generate_pdf: "Generate PDF" button_create: "Crear" diff --git a/config/locales/crowdin/js-de.yml b/config/locales/crowdin/js-de.yml index 04679c2459f..e380b584676 100644 --- a/config/locales/crowdin/js-de.yml +++ b/config/locales/crowdin/js-de.yml @@ -138,7 +138,7 @@ de: description_available_columns: "Verfügbare Spalten" description_current_position: "Sie sind hier: " description_select_work_package: "Arbeitspaket #%{id} auswählen" - description_subwork_package: "Unteraufgabe von Arbeitspaket #%{id}" + description_subwork_package: "Kind von Arbeitspaket #%{id}" editor: revisions: "Lokale Änderungen anzeigen" no_revisions: "Keine lokalen Änderungen gefunden" @@ -455,7 +455,7 @@ de: label_total_progress: "%{percent}% Gesamtfortschritt" label_total_amount: "Gesamt: %{amount}" label_updated_on: "aktualisiert am" - label_value_derived_from_children: "(aggregierter Wert von Unteraufgaben)" + label_value_derived_from_children: "(aggregierter Wert von Kindelementen)" label_children_derived_duration: "Aggregierte Dauer der Unteraufgaben" label_warning: "Warnung" label_work_package: "Arbeitspaket" @@ -864,7 +864,7 @@ de: title: "Neues Arbeitspaket" header: "Neu: %{type}" header_no_type: "Neues Arbeitspaket (Typ noch nicht gesetzt)" - header_with_parent: "Neu: %{type} (Unteraufgabe von %{parent_type} #%{id})" + header_with_parent: "Neu: %{type} (Kind von %{parent_type} #%{id})" button: "Erstellen" duplicate: title: "Arbeitspaket duplizieren" @@ -1061,7 +1061,7 @@ de: single_text: "Sind Sie sicher, dass Sie das Arbeitspaket löschen möchten?" bulk_text: "Sind Sie sicher, dass Sie die folgenden %{label} löschen möchten?" has_children: "Dieses Arbeitspaket hat %{childUnits}:" - confirm_deletion_children: "Ich bestätige, dass alle Unteraufgaben der hier aufgeführten Arbeitspakete rekursiv entfernt werden." + confirm_deletion_children: "Ich bestätige, dass alle untergordneten Elemente der hier aufgeführten Arbeitspakete rekursiv entfernt werden." deletes_children: "Alle Unteraufgaben und deren Nachkommen werden auch rekursiv gelöscht." destroy_time_entry: title: "Löschen der Zeitbuchung bestätigen" diff --git a/config/locales/crowdin/js-ja.yml b/config/locales/crowdin/js-ja.yml index 5b741ef8b29..eb2d9c4f72b 100644 --- a/config/locales/crowdin/js-ja.yml +++ b/config/locales/crowdin/js-ja.yml @@ -32,12 +32,12 @@ ja: draggable_hint: | 埋め込み画像または添付ファイルをエディタにドラッグします。 ドラッグしつづけると閉じているエディタ領域が開きます。 - quarantined_hint: "ウイルスが発見されたように、ファイルは隔離されています。ダウンロードできません。" + quarantined_hint: "ウイルスが発見されたため,ファイルは隔離されています。ダウンロードできません。" autocomplete_ng_select: - add_tag: "アイテムを追加" + add_tag: "項目を追加" clear_all: "すべてクリア" loading: "読み込み中..." - not_found: "アイテムが見つかりません" + not_found: "見つかりませんでした" type_to_search: "検索キーワードを入力" autocomplete_select: placeholder: @@ -67,7 +67,7 @@ ja: button_back_to_list_view: "リスト表示に戻る" button_cancel: "キャンセル" button_close: "閉じる" - button_change_project: "別のプロジェクトに移動" + button_change_project: "他のプロジェクトに移る" button_check_all: "全てを選択" button_configure-form: "フォームを設定" button_confirm: "確認" @@ -75,7 +75,7 @@ ja: button_copy: "コピー" button_copy_to_clipboard: "クリップボードにコピー" button_copy_link_to_clipboard: "クリップボードにリンクをコピー" - button_copy_to_other_project: "別のプロジェクトで複製" + button_copy_to_other_project: "別のプロジェクトで複製する" button_custom-fields: "カスタムフィールド" button_delete: "削除" button_delete_watcher: "ウォッチャーを削除" @@ -97,7 +97,7 @@ ja: button_open_fullscreen: "全画面表示を開く" button_show_cards: "カードビュー表示" button_show_list: "リストビュー表示" - button_show_table: "テーブルビューを表示" + button_show_table: "テーブル表示" button_show_gantt: "ガントビューを表示" button_show_fullscreen: "全画面表示" button_more_actions: "その他の操作" @@ -107,7 +107,7 @@ ja: button_uncheck_all: "全てを選択解除" button_update: "更新" button_export-atom: "Atomをダウンロード" - button_generate_pdf: "PDFを生成" + button_generate_pdf: "PDF作成" button_create: "作成" card: add_new: "新規カード追加" @@ -141,8 +141,8 @@ ja: description_select_work_package: "ワークパッケージを選択 #%{id}" description_subwork_package: "ワークパッケージの子 #%{id}" editor: - revisions: "ローカルの変更を表示" - no_revisions: "ローカルの変更は見つかりませんでした" + revisions: "ローカルの修正を表示" + no_revisions: "ローカルでの修正は見つからず" preview: "プレビューモードの切り替え" source_code: "Markdown ソースモードの切り替え" error_saving_failed: "次のエラーで文書を保存するのに失敗しました: %{error}" @@ -155,7 +155,7 @@ ja: attribute_reference: macro_help_tooltip: "このテキストセグメントはマクロによって動的にレンダリングされています。" not_found: "要求されたリソースが見つかりませんでした" - nested_macro: "このマクロは %{model} %{id} を再帰的に参照しています。" + nested_macro: "このマクロは %{model} %{id}を再帰的に参照している。" invalid_attribute: "選択した属性 '%{name}' は存在しません。" child_pages: button: "子ページへのリンク" @@ -211,10 +211,10 @@ ja: calendar: empty_state_header: "休業日" empty_state_description: '休業日が定義されていません。「休業日を追加」ボタンをクリックして日付を追加してください。' - new_date: "(新規)" + new_date: "(新)" add_non_working_day: "休業日を追加" - already_added_error: "この日付の非作業日はすでに存在します。それぞれの日付に1つの非作業日が作成されます。" - change_button: "保存してスケジュールを変更" + already_added_error: "この日付の非営業日はすでに存在します。一意の日付に対して作成できる非営業日は1つだけです。" + change_button: "保存して再スケジュール" change_title: "営業日を変更する" removed_title: "以下の日を非稼働日リストから削除します:" change_description: "営業日とみなす曜日を変更すると、このサイト内のすべてのプロジェクトのすべてのワークパッケージの開始日と終了日に影響を与える可能性があります。" @@ -296,14 +296,14 @@ ja: ical_sharing_modal: title: "カレンダーを購読する" inital_setup_error_message: "データ取得中にエラーが発生しました。" - description: "URL(iCalendar)を使って外部クライアントでこのカレンダーを購読し、そこから最新のワークパッケージ情報を見ることができます。" - warning: "このURLを他のユーザーと共有しないでください。このリンクがあれば、誰でもアカウントやパスワードなしでワークパッケージの詳細を見ることができます。" - token_name_label: "どこで使うのですか?" + description: "URL(iCalendar)を使用して、外部クライアントでこのカレンダーを購読し、そこから最新の作業パッケージ情報を表示することができます。" + warning: "このURLを他のユーザーと共有しないでください。このリンクを持つ誰でもアカウントやパスワードなしでワークパッケージの詳細を表示することができます。" + token_name_label: "どこで使うのですか??" token_name_placeholder: '名前を入力してください。例:"電話"' token_name_description_text: 'If you subscribe to this calendar from multiple devices, this name will help you distinguish between them in your access tokens list.' copy_url_label: "URLをコピー" - ical_generation_error_text: "カレンダー URL の生成中にエラーが発生しました。" - success_message: 'URL "%{name}" は正常にクリップボードにコピーされました。サブスクリプションを完了するためにカレンダークライアントに貼り付けてください。' + ical_generation_error_text: "カレンダーのURL生成時にエラーが発生しました。" + success_message: 'URL "%{name}" がクリップボードにコピーされました。カレンダークライアントに貼り付けて購読を完了してください。' label_activate: "有効にする" label_assignee: "担当者" label_assignee_alt_text: "This work package is assigned to %{name}" @@ -316,7 +316,7 @@ ja: label_add_row_before: "前に行を追加" label_add_selected_columns: "選択した列を追加" label_added_by: "追加した人" - label_added_time_by: '%{author} が %{age} に追加しました' + label_added_time_by: '追加 %{author} %{age}' label_ago: "○日前" label_all: "全て" label_all_projects: "すべてのプロジェクト" @@ -429,7 +429,7 @@ ja: label_repository_plural: "リポジトリ" label_resize_project_menu: "Resize project menu" label_save_as: "名前をつけて保存" - label_search_columns: "列を検索" + label_search_columns: "列を検索する" label_select_watcher: "ウォッチャーを選択..." label_selected_filter_list: "選択されたフィルタ" label_show_attributes: "すべての属性を表示" @@ -467,8 +467,8 @@ ja: label_watch_work_package: "ワークパッケージをウォッチ" label_watcher_added_successfully: "ウォッチャーが正常に追加されました !" label_watcher_deleted_successfully: "ウォッチャーが正常に削除されました !" - label_work_package_details_you_are_here: "あなたは %{tab} %{type} %{subject} のタブにいます。" - label_work_package_context_menu: "ワークパッケージのコンテキスト メニュー" + label_work_package_details_you_are_here: "あなたは %{type} %{subject}の %{tab} タブを表示しています。" + label_work_package_context_menu: "作業パッケージのコンテキストメニュー" label_unwatch: "ウォッチしない" label_unwatch_work_package: "ワークパッケージのウォッチを削除" label_uploaded_by: "アップロードした人" @@ -499,7 +499,7 @@ ja: label_version_plural: "バージョン" label_view_has_changed: "このビューには未保存の変更があります。 クリックすると保存します。" help_texts: - show_modal: "ヘルプテキストを表示" + show_modal: "ヘルプテキストを表示する" onboarding: buttons: skip: "スキップ" @@ -507,7 +507,7 @@ ja: got_it: "了承" steps: help_menu: "ヘルプ(?)メニューは、その他のヘルプリソースを提供します。ここでは、ユーザーガイド、役立つハウツービデオなどを見つけることができます。
OpenProjectでの作業をお楽しみください!" - members: "新しい メンバー をプロジェクトに招待します。" + members: "新しいメンバーをプロジェクトに招待する。" quick_add_button: "ヘッダーナビゲーションにあるプラス(+)アイコンをクリックして、新規プロジェクトを作成したり、同僚を招待したりできます。" sidebar_arrow: "プロジェクトのメインメニューに戻るには、左上の矢印を使います。" welcome: "3分間のイントロダクションツアーで、最も重要な機能を学びましょう。
最後までステップを完了することをお勧めします。ツアーはいつでも再開できます。" @@ -614,33 +614,33 @@ ja: work_package_commented: "すべての新着コメント" work_package_created: "新しいワークパッケージ" work_package_processed: "すべてのステータス変更" - work_package_prioritized: "すべての優先度の変更" - work_package_scheduled: "すべての日付の変更" + work_package_prioritized: "すべての優先順位の変更" + work_package_scheduled: "すべての日付変更" global: immediately: title: "参加" - description: "自分が関与しているワークパッケージのすべてのアクティビティに関する通知(アサイニー、アカウンタブル、ウォッチャー)。" + description: "自分が関与しているワークパッケージのすべてのアクティビティに関する通知(担当、責任、ウォッチャー)。" delayed: title: "不参加" - description: "すべてのプロジェクトでのアクティビティの追加通知。" + description: "全プロジェクトにおける活動の追加通知。" date_alerts: title: "日付アラート" - description: "あなたが関与している(アサイニー、アカウンタブル、ウォッチャー)オープンワークパッケージの重要な日付が近づくと自動通知。" + description: "あなたが関与している(担当、責任、ウォッチャー)オープンワークパッケージの重要な日付が近づくと自動通知。" overdue: 期限を過ぎた場合 project_specific: title: "プロジェクト固有の通知設定" - description: "これらのプロジェクト固有の設定は、上記のデフォルト設定を上書きする。" + description: "これらのプロジェクト固有の設定は、上記のデフォルト設定を上書きします。" add: "プロジェクトの設定を追加する" - already_selected: "このプロジェクトは既に選択されています" + already_selected: "このプロジェクトはすでに選ばれている" remove: "プロジェクトの設定を削除する" pagination: no_other_page: "このページだけです。" - pages_skipped: "ページがスキップされました。" + pages_skipped: "ページスキップ。" page_navigation: "ページネーション・ナビゲーション" per_page_navigation: 'ページ毎のアイテム選択' pages: page_number: ページ %{number} - show_per_page: ページあたり %{number} を表示 + show_per_page: ページごとに %{number} placeholders: default: "-" subject: "ここにタイトルを入力します" @@ -650,7 +650,7 @@ ja: project: autocompleter: label: "プロジェクト名の入力補完" - click_to_switch_to_project: "プロジェクト: %{projectname}" + click_to_switch_to_project: "プロジェクト: %{projectname}" context: "プロジェクトのコンテキスト" not_available: "プロジェクトなし" required_outside_context: > @@ -658,30 +658,30 @@ ja: reminders: settings: daily: - add_time: "時間を追加" + add_time: "時間を追加する" enable: "毎日のEメールリマインダーを有効にする" explanation: "このリマインダーは、未読の通知に対してのみ、指定した時間帯にのみ届きます。 %{no_time_zone}" no_time_zone: "アカウントにタイムゾーンを設定するまでは、時間はUTCで解釈されます。" time_label: "時間 %{counter}:" - title: "未読の通知を毎日メールで通知する" + title: "未読通知メールのリマインダーを毎日送信する" workdays: title: "これらの日にリマインダーメールを受け取る" immediate: title: "電子メールのリマインダーを送信" mentioned: "@mentionするとすぐに" - personal_reminder: "個人的なリマインダーを受け取ったら直ちに" + personal_reminder: "個人的なリマインダーを受け取ったとき" alerts: title: "その他の項目(ワークパッケージではないもの)に対する電子メールアラート" explanation: > 本日の通知はワークパッケージに限定されています。これらのイベントが通知に含まれるようになるまで、Eメールアラートを受信し続けることを選択できます: news_added: "ニュースが追加されました。" news_commented: "ニュースへのコメント" - document_added: "追加された書類" + document_added: "ドキュメントの追加" forum_messages: "新しいフォーラムメッセージ" wiki_page_added: "Wikiページが追加されました。" wiki_page_updated: "Wikiページが更新されました。" - membership_added: "メンバーシップが追加されました" - membership_updated: "メンバーシップ更新" + membership_added: "メンバーシップの追加" + membership_updated: "メンバーシップの更新" title: "電子メールによるリマインダー" pause: label: "毎日のEメールリマインダーを一時停止する" @@ -1172,7 +1172,7 @@ ja: toggle_title: "ベースライン" clear: "クリア" apply: "適用" - header_description: "過去のいずれかの時点からこのリストに加えられた変更を強調する。" + header_description: "過去の選択した時点からこのリストに加えられた変更をハイライト" show_changes_since: "以降の変更を表示する" help_description: "ベースラインの基準タイムゾーン。" time_description: "現地時間: %{datetime}" diff --git a/config/locales/crowdin/js-no.yml b/config/locales/crowdin/js-no.yml index 37d78447d18..1594fc635ae 100644 --- a/config/locales/crowdin/js-no.yml +++ b/config/locales/crowdin/js-no.yml @@ -104,7 +104,7 @@ button_save: "Lagre" button_settings: "Innstillinger" button_uncheck_all: "Avmerk alle" - button_update: "Oppdater" + button_update: "Oppdatèr" button_export-atom: "Last ned Atom" button_generate_pdf: "Generate PDF" button_create: "Opprett" diff --git a/config/locales/crowdin/js-ro.yml b/config/locales/crowdin/js-ro.yml index 16b185d9890..7b76eb3d468 100644 --- a/config/locales/crowdin/js-ro.yml +++ b/config/locales/crowdin/js-ro.yml @@ -104,7 +104,7 @@ ro: button_save: "Salvează" button_settings: "Setări" button_uncheck_all: "Deselectează tot" - button_update: "Actualizează" + button_update: "Actualizare" button_export-atom: "Descarcă Atom" button_generate_pdf: "Generează PDF" button_create: "Creează" diff --git a/config/locales/crowdin/js-ru.yml b/config/locales/crowdin/js-ru.yml index 9a21927c4a5..66046d33eda 100644 --- a/config/locales/crowdin/js-ru.yml +++ b/config/locales/crowdin/js-ru.yml @@ -104,7 +104,7 @@ ru: button_save: "Сохранить" button_settings: "Настройки" button_uncheck_all: "Снять все отметки" - button_update: "Обновить" + button_update: "Обновление" button_export-atom: "Скачать Atom" button_generate_pdf: "Создать PDF" button_create: "Создать" diff --git a/config/locales/crowdin/ro.yml b/config/locales/crowdin/ro.yml index b06bcfab59e..67970a3d530 100644 --- a/config/locales/crowdin/ro.yml +++ b/config/locales/crowdin/ro.yml @@ -3238,7 +3238,7 @@ ro: label_duplicated_by: "dublat de" label_duplicate: "duplicat" label_duplicates: "dublează" - label_edit: "Editează" + label_edit: "Editare" label_edit_attribute: "Edit attribute" label_edit_x: "Editare: %{x}" label_enable_multi_select: "Comutare selecție multiplă" @@ -3296,7 +3296,7 @@ ro: label_global_roles: "Roluri globale" label_git_path: "Calea catre directorul .git" label_greater_or_equal: ">=" - label_group_by: "Grupează după" + label_group_by: "Grupare după" label_group_new: "Grupare nouă" label_group: "Grup" label_group_named: "Grup %{name}" diff --git a/config/locales/crowdin/sl.yml b/config/locales/crowdin/sl.yml index d3855d7d74d..24a5b6878d1 100644 --- a/config/locales/crowdin/sl.yml +++ b/config/locales/crowdin/sl.yml @@ -2382,8 +2382,8 @@ sl: - "avgust" - "september" - "oktober" - - "november" - - "december" + - "November" + - "December" order: - :leto - :mesec diff --git a/config/locales/crowdin/uk.yml b/config/locales/crowdin/uk.yml index e2b28ee3117..1010e617be2 100644 --- a/config/locales/crowdin/uk.yml +++ b/config/locales/crowdin/uk.yml @@ -3366,7 +3366,7 @@ uk: label_index_by_title: "Індекс за назвою" label_information: "Інформація" label_information_plural: "Інформація" - label_installation_guides: "Інструкції із встановлення" + label_installation_guides: "Інструкції зі встановлення" label_integer: "Ціле число" label_interface: "Інтерфейс" label_internal: "Власне" diff --git a/config/locales/crowdin/zh-CN.seeders.yml b/config/locales/crowdin/zh-CN.seeders.yml index 3e5b9f8d136..d6ad2b48763 100644 --- a/config/locales/crowdin/zh-CN.seeders.yml +++ b/config/locales/crowdin/zh-CN.seeders.yml @@ -97,7 +97,7 @@ zh-CN: demo-project: name: 演示项目 status_explanation: 所有任务都按计划进行。相关人员均知晓各自任务。系统已完全建立。 - description: 这是对此演示 Scrum 项目目标的简短摘要。 + description: 这是对此演示项目目标的简短摘要。 news: item_0: title: 欢迎来到您的演示项目 @@ -216,7 +216,7 @@ zh-CN: scrum-project: name: Scrum 项目 status_explanation: 所有任务都按计划进行。相关人员均知晓各自任务。系统已完全建立。 - description: 这是对此演示 Scrum 项目目标的简短摘要。 + description: 这是对此演示Scrum项目目标的简短摘要。 news: item_0: title: 欢迎来到您的 Scrum 演示项目 diff --git a/config/locales/crowdin/zh-CN.yml b/config/locales/crowdin/zh-CN.yml index b7534860153..35d766e4540 100644 --- a/config/locales/crowdin/zh-CN.yml +++ b/config/locales/crowdin/zh-CN.yml @@ -87,7 +87,7 @@ zh-CN: token_placeholder: "在此处粘贴您的企业版支持令牌" add_token: "上传企业版支持令牌" replace_token: "替换您当前的支持令牌" - order: "订购本地部署版的 Enterprise edition" + order: "订购本地部署的 Enterprise edition" paste: "粘贴您企业版的支持令牌" required_for_feature: "此功能仅限具激活的企业版支持令牌的订阅者使用。" enterprise_link: "如需了解详细信息,请单击此处。" @@ -1206,7 +1206,7 @@ zh-CN: page: "页" row_count: "行数" column_count: "列数" - widgets: "微件" + widgets: "小部件" journal: notes: "备注" cause_type: "Cause 类型" @@ -3475,7 +3475,7 @@ zh-CN: label_revision_id: "修订版本 %{value}" label_revision_plural: "修订" label_roadmap: "路线图" - label_roadmap_edit: "编辑路线图%{name}" + label_roadmap_edit: "编辑路线图 %{name}" label_roadmap_due_in: "%{value} 到期" label_roadmap_no_work_packages: "该版本没有工作包。" label_roadmap_overdue: "%{value} 超时" @@ -4196,7 +4196,7 @@ zh-CN: managed: "在 OpenProject 中创建新的存储库" storage: not_available: "磁盘存储开销不可用于此存储库。" - update_timeout: "在 N 分钟内保留存储库最后所需的磁盘空间信息。由于计算存储库所需的磁盘空间可能增加系统开销,增加该值可以减少性能影响。" + update_timeout: "在 N 分钟内保留存储库最后所需磁盘空间的信息。由于计算存储库所需的磁盘空间可能增加系统开销,增加该值可以减少性能影响。" oauth_application_details: "关闭此窗口后,将无法再次访问客户端密钥值。请将这些值复制到 Nextcloud OpenProject 集成设置中:" oauth_application_details_link_text: "转到设置页面" setup_documentation_details: "如果您在配置新文件存储方面需要帮助,请查看文档:" @@ -4405,7 +4405,7 @@ zh-CN: setting_session_ttl_hint: "当设置的值低于5时,其作用类似于禁用。" setting_session_ttl_enabled: "会话过期" setting_start_of_week: "一周起始日" - setting_sys_api_enabled: "启用存储库管理网页服务" + setting_sys_api_enabled: "启用版本库管理 web 服务" setting_sys_api_description: "存储库管理网页服务提供了集成的,用户授权的存储库访问。" setting_time_format: "时间" setting_total_percent_complete_mode: "计算 完成% 层次结构总数" @@ -4834,7 +4834,7 @@ zh-CN: warning_user_limit_reached_admin: > 添加额外的用户将超出当前限制。请升级您的计划,以确保外部用户能够访问此实例。 warning_user_limit_reached_instructions: > - 您已达到用户限制(%{current}/%{max} 活跃用户)。请联系 sales@openproject.com 升级您的企业版计划以添加额外用户。 + 您达到了用户限制(%{current}/%{max}活跃用户)。 请联系sales@openproject.com以升级您的Enterprise edition计划并添加其他用户。 warning_protocol_mismatch_html: > warning_bar: diff --git a/config/locales/crowdin/zh-TW.yml b/config/locales/crowdin/zh-TW.yml index 60971668aec..d999654ef89 100644 --- a/config/locales/crowdin/zh-TW.yml +++ b/config/locales/crowdin/zh-TW.yml @@ -3178,7 +3178,7 @@ zh-TW: label_filter_add: "新增條件" label_filter_by: "篩選條件:" label_filter_any_name_attribute: "名稱屬性" - label_filter_plural: "篩選條件" + label_filter_plural: "篩選器" label_filters_toggle: "顯示/隱藏篩選條件" label_float: "浮點數" label_folder: "資料夾" @@ -3193,8 +3193,8 @@ zh-TW: label_global_modules: "全域模組" label_global_roles: "全域角色" label_git_path: ".git 目錄的路徑" - label_greater_or_equal: "之前" - label_group_by: "分類" + label_greater_or_equal: ">=" + label_group_by: "分組依據" label_group_new: "新增群組" label_group: "群組" label_group_named: "群組名稱 %{name}" @@ -3206,7 +3206,7 @@ zh-TW: label_hierarchy: "階層" label_hierarchy_leaf: "頁面結構頁" label_home: "Home" - label_subject_or_id: "名稱或 id" + label_subject_or_id: "主旨或 id" label_calendar_subscriptions: "訂閱行事曆" label_identifier: "識別碼" label_in: "在" @@ -3256,7 +3256,7 @@ zh-TW: label_latest_revision_plural: "最新版本" label_ldap_authentication: "LDAP 認證" label_learn_more: "了解更多" - label_less_or_equal: "之後" + label_less_or_equal: "<=" label_less_than_ago: "幾天內" label_link_url: "連結(URL)" label_list: "清單" diff --git a/modules/backlogs/config/locales/crowdin/ro.yml b/modules/backlogs/config/locales/crowdin/ro.yml index 0738e8f286e..4b6260593aa 100644 --- a/modules/backlogs/config/locales/crowdin/ro.yml +++ b/modules/backlogs/config/locales/crowdin/ro.yml @@ -28,7 +28,7 @@ ro: work_package: position: "Poziție" story_points: "Puncte" - backlogs_work_package_type: "Tipul de restante" + backlogs_work_package_type: "Tip restanță" errors: models: work_package: diff --git a/modules/backlogs/config/locales/crowdin/zh-TW.yml b/modules/backlogs/config/locales/crowdin/zh-TW.yml index ed6bdbb3838..0de02e9396d 100644 --- a/modules/backlogs/config/locales/crowdin/zh-TW.yml +++ b/modules/backlogs/config/locales/crowdin/zh-TW.yml @@ -21,7 +21,7 @@ #++ zh-TW: plugin_openproject_backlogs: - name: "OpenProject待辦事項" + name: "OpenProject代辦事項" description: "此模組新增了讓敏捷團隊能夠在 Scrum 專案中使用 OpenProject 的功能。" activerecord: attributes: diff --git a/modules/bim/config/locales/crowdin/da.seeders.yml b/modules/bim/config/locales/crowdin/da.seeders.yml index 9e4339bc6ec..acc5260021b 100644 --- a/modules/bim/config/locales/crowdin/da.seeders.yml +++ b/modules/bim/config/locales/crowdin/da.seeders.yml @@ -179,21 +179,21 @@ da: options: name: At komme gang text: | - We are glad you joined! We suggest to try a few things to get started in OpenProject. + Vi er glade for, at du er med! Vi foreslår, at du prøver et par ting for at komme i gang med OpenProject. - Here you will find the classical roles, some workflows and work packages for your construction project. + Her finder du de klassiske roller, nogle arbejdsgange og arbejdspakker til dit byggeprojekt. - _Try the following steps:_ + Prøv følgende trin:_ - 1. _Invite new members to your project:_ → Go to [Members]({{opSetting:base_url}}/projects/demo-planning-constructing-project/members) in the project navigation. - 2. _View the work in your projects:_ → Go to [Work packages]({{opSetting:base_url}}/projects/demo-planning-constructing-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22bcfIssueAssociated%22%2C%22o%22%3A%22%3D%22%2C%22v%22%3A%5B%22f%22%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D) in the project navigation. - 3. _Create a new work package:_ → Go to [Work packages → Create]({{opSetting:base_url}}/projects/demo-planning-constructing-project/work_packages/new?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22bcfIssueAssociated%22%2C%22o%22%3A%22%3D%22%2C%22v%22%3A%5B%22f%22%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D&type=11). - 4. _Create and update a Gantt chart:_ → Go to [Gantt chart]({{opSetting:base_url}}/projects/demo-planning-constructing-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22assignee%22%2C%22responsible%22%5D%2C%22tv%22%3Atrue%2C%22tzl%22%3A%22weeks%22%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D) in the project navigation. - 5. _Activate further modules:_ → Go to [Project settings → Modules]({{opSetting:base_url}}/projects/demo-planning-constructing-project/settings/modules). - 6. _Working agile? Create a new board:_ → Go to [Boards]({{opSetting:base_url}}/projects/demo-planning-constructing-project/boards) + 1. _Invitér nye medlemmer til dit projekt:_ → Gå til [Medlemmer]({{opSetting:base_url}}/projects/demo-planning-constructing-project/members) i projektnavigationen. + 2. _Se arbejdet i dine projekter:_ → Gå til [Arbejdspakker]({{opSetting:base_url}}/projects/demo-planning-constructing-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22bcfIssueAssociated%22%2C%22o%22%3A%22%3D%22%2C%22v%22%3A%5B%22f%22%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D) i projektnavigationen. + 3. _Create a new work package:_ → Gå til [Work packages → Create]({{opSetting:base_url}}/projects/demo-planning-constructing-project/work_packages/new?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22bcfIssueAssociated%22%2C%22o%22%3A%22%3D%22%2C%22v%22%3A%5B%22f%22%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D&type=11). + 4. _Create and update a Gantt chart:_ → Go to [Gantt chart]({{opSetting:base_url}}/projects/demo-planning-constructing-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22assignee%22%2C%22responsible%22%5D%2C%22tv%22%3Atrue%2C%22tzl%22%3A%22weeks%22%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D) i projektnavigationen. + 5. _Aktivér yderligere moduler:_ → Gå til [Projektindstillinger → Moduler]({{opSetting:base_url}}/projects/demo-planning-constructing-project/settings/modules). + 6. _Arbejder du agilt? Opret et nyt board:_ → Gå til [Boards]({{opSetting:base_url}}/projects/demo-planning-constructing-project/boards) - Here you will find our [User Guides](https://www.openproject.org/docs/user-guide/). - Please let us know if you have any questions or need support. Contact us: [support\[at\]openproject.com](mailto:support@openproject.com). + Her finder du vores [User Guides](https://www.openproject.org/docs/user-guide/). + Lad os vide, hvis du har spørgsmål eller brug for support. Kontakt os gerne: [support\[at\]openproject.com](mailto:support@openproject.com). item_4: options: name: Medlemmer @@ -205,207 +205,207 @@ da: name: Milepæle work_packages: item_0: - subject: Project kick off construction project + subject: Projekt kick off anlægsprojekt description: |- - The project kick off initializes the start of the project in your company. Everybody being part of this project should be invited to the kick off for a first briefing. + Projektets kick off markerer starten på projektet i din virksomhed. Alle, der er en del af projektet, bør inviteres til kick off for at få en første briefing. - The next step could be checking out the timetable and adjusting the appointments, by looking at the [Gantt chart]({{opSetting:base_url}}/projects/demo-construction-project/work_packages?query_props=%7B%22c%22%3A%5B%22id%22%2C%22subject%22%2C%22startDate%22%2C%22dueDate%22%5D%2C%22tv%22%3Atrue%2C%22tzl%22%3A%22weeks%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22status%22%2C%22o%22%3A%22o%22%2C%22v%22%3A%5B%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%7D). + Det næste skridt kunne være at tjekke tidsplanen og justere aftalerne ved at se på [Gantt-diagrammet]({{opSetting:base_url}}/projects/demo-construction-project/work_packages?query_props=%7B%22c%22%3A%5B%22id%22%2C%22subject%22%2C%22startDate%22%2C%22dueDate%22%5D%2C%22tv%22%3Atrue%2C%22tzl%22%3A%22weeks%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22status%22%2C%22o%22%3A%22o%22%2C%22v%22%3A%5B%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%7D). item_1: - subject: Basic evaluation - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + subject: Grundlæggende evaluering + description: Denne type er hierarkisk overordnet typerne "Clash" og "Request", og repræsenterer således en generel note. children: item_0: - subject: Gathering first project information + subject: Indsamling af de første projektoplysninger description: |- - ## Goal + ## Mål - * Define tasks based on the customer needs - * Time frame and cost estimation shall be defined + * Definer opgaver baseret på kundens behov + * Tidsramme og omkostningsoverslag skal defineres - ## Description + ## Beskrivelse - * Identify the customer needs by having a workshop with him/ her - * Each need shall represent a task with its corresponding work packages - * Derive the cost estimation and time frame + * Identificer kundens behov ved at afholde en workshop med ham/hende + * Hvert behov skal repræsentere en opgave med tilhørende arbejdspakker + * Udled omkostningsoverslag og tidsramme item_1: - subject: Summarize the results + subject: Sammenfat resultaterne description: |- - ## Goal + ## Mål - * Create a useful overview of the results - * Check what has been done and summarize the results - * Communicate all the relevant results with the customer - * Identify the fundamental boundary conditions of the project + * At skabe et brugbart overblik over resultaterne + * At kontrollere, hvad der er blevet gjort, og sammenfatte resultaterne + * At kommunikere alle relevante resultater med kunden + * At identificere projektets grundlæggende grænsebetingelser - ## Description + ## Beskrivelse - * Each topic gets its own overview which will be used as a catalogue of results - * This overview informs all participants about the decisions made - * ... + * Hvert emne får sit eget overblik, som vil blive brugt som et resultatkatalog + * Dette overblik informerer alle deltagere om de beslutninger, der er truffet + * ... item_2: - subject: End of basic evaluation - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + subject: Afslutning af grundlæggende evaluering + description: Denne type er hierarkisk overordnet typerne "Clash" og "Request", og repræsenterer således en generel note. item_2: - subject: Preliminary planning - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + subject: Indledende planlægning + description: Denne type er hierarkisk overordnet typerne "Clash" og "Request", og repræsenterer således en generel note. children: item_0: - subject: Developing first draft + subject: Udvikling af første udkast description: |- - ## Goal + ## Mål - * Create a useful overview of the results - * Check what has been done and summarize the results - * Communicate all the relevant results with the customer - * Identify the fundamental boundary conditions of the project + * At skabe et brugbart overblik over resultaterne + * At kontrollere, hvad der er blevet gjort, og sammenfatte resultaterne + * At kommunikere alle relevante resultater med kunden + * At identificere projektets grundlæggende grænsebetingelser - ## Description + ## Beskrivelse - * Each topic gets its own overview which will be used as a catalogue of results - * This overview informs all participants about the decisions made - * ... + * Hvert emne får sit eget overblik, som vil blive brugt som et resultatkatalog + * Dette overblik informerer alle deltagere om de beslutninger, der er truffet + * ... item_1: - subject: Summarize results + subject: Sammenfat resultaterne description: |- - ## Goal + ## Mål - * Create a useful overview of the results - * Check what has been done and summarize the results - * Communicate all the relevant results with the customer - * Identify the fundamental boundary conditions of the project + * At skabe et brugbart overblik over resultaterne + * At kontrollere, hvad der er blevet gjort, og sammenfatte resultaterne + * At kommunikere alle relevante resultater med kunden + * At identificere projektets grundlæggende grænsebetingelser - ## Description + ## Beskrivelse - * Each topic gets its own overview which will be used as a catalogue of results - * This overview informs all participants about the decisions made - * ... + * Hvert emne får sit eget overblik, som vil blive brugt som et resultatkatalog + * Dette overblik informerer alle deltagere om de beslutninger, der er truffet + * ... item_3: - subject: Passing of preliminary planning - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + subject: Godkendelse af foreløbig planlægning + description: Denne type er hierarkisk overordnet typerne "Clash" og "Request", og repræsenterer således en generel note. item_4: - subject: Design planning - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + subject: Planlægning af design + description: Denne type er hierarkisk overordnet typerne "Clash" og "Request", og repræsenterer således en generel note. children: item_0: - subject: Finishing design + subject: Efterbehandling af design description: |- - ## Goal + ## Mål - * Design is done - * All parties are happy with the results of the design planning phase + * Designet er færdigt + * Alle parter er tilfredse med resultaterne af designplanlægningsfasen - ## Description + ## Beskrivelse - * The design of the project will be finished - * All parties agree on the design - * The owner is happy with the results - * ... + * Designet af projektet bliver færdigt + * Alle parter er enige om designet + * Bygherren er tilfreds med resultaterne + * ... item_1: - subject: Design freeze - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + subject: Fastfrysning af design + description: Denne type er hierarkisk overordnet typerne "Clash" og "Request", og repræsenterer således en generel note. item_5: - subject: Construction phase + subject: Konstruktionsfasen children: item_0: - subject: Start constructing + subject: Begynd at bygge description: |- - ## Goal + ## Mål - * Ground breaking ceremony - * Setting up the construction site - * ... + * Første spadestik + * Etablering af byggeplads + * ... - ## Description + ## Beskrivelse - * Preparing the site for the project - * Get the team together - * ... + * Klargøring af byggepladsen til projektet + * Saml holdet + * ... item_1: - subject: Foundation + subject: Fundament description: |- - ## Goal + ## Mål - * Laying of the foundation stone - * ... + * Lægning af grundsten + * ... - ## Description + ## Beskrivelse - * Setting up the concrete mixer - * Setting up the supply chain for the concrete - * ... + * Opsætning af betonblanderen + * Opsætning af forsyningskæden til betonen + * ... item_2: - subject: Building construction + subject: Bygningskonstruktion description: |- - ## Goal + ## Mål - * Topping out ceremony - * Walls and ceilings are done - * ... + * Topping out-ceremoni + * Vægge og lofter er færdige + * ... - ## Description + ## Beskrivelse - * Creating all structural levels of the building - * Installing doors and windows - * Finishing the roof structure - * ... + * Oprettelse af alle strukturelle niveauer i bygningen + * Installation af døre og vinduer + * Færdiggørelse af tagkonstruktionen + * ... item_3: - subject: Finishing the facade + subject: Færdiggørelse af facaden description: |- - ## Goal + ## Mål - * Facade is done - * Whole building is waterproof - * ... + * Facaden er færdig + * Hele bygningen er vandtæt + * ... - ## Description + ## Beskrivelse - * Install all elements for the facade - * Finish the roof - * ... + * Monter alle elementer til facaden + * Gør taget færdigt + * ... item_4: - subject: Installing the building service systems + subject: Installation af bygningens tekniske systemer description: |- - ## Goal + ## Mål - * All building service systems are ready to be used + * Alle bygningens tekniske systemer er klar til brug - ## Description + ## Beskrivelse - * Installing the heating system - * Installing the climate system - * Electrical installation - * ... + * Installation af varmesystemet + * Installation af klimasystemet + * El-installation + * ... item_5: - subject: Final touches + subject: Afsluttende detaljer description: |- - ## Goal + ## Mål - * Handover of the keys - * The customer is happy with his building - * ... + * Aflevering af nøgler + * Kunden er tilfreds med sit byggeri + * ... - ## Description + ## Beskrivelse - * Finishing the installation of the building service systems - * Finishing the interior construction - * Finishing the facade - * ... + * Færdiggørelse af installationen af bygningens tekniske systemer + * Færdiggørelse af den indvendige konstruktion + * Færdiggørelse af facaden + * ... item_6: - subject: House warming party + subject: Indvielsesfest i huset description: |- - ## Goal + ## Mål - * Have a blast! + * Hav det sjovt! - ## Description + ## Beskrivelse - * Invite the construction team - * Invite your friends - * Bring some drinks, snacks and your smile + * Invitér byggeholdet + * Invitér dine venner + * Medbring drikkevarer, snacks og dit smil demo-bim-project: - name: "(Demo) BIM project" - status_explanation: All tasks and sub-projects are on schedule. The people involved know their tasks. The system is completely set up. - description: This is a short summary of the goals of this demo BIM project. + name: "(Demo) BIM-projekt" + status_explanation: Alle opgaver og delprojekter følger tidsplanen. De involverede kender deres opgaver. Systemet er fuldt opsat. + description: Dette er et kort resumé af målene for dette demobyggeprojekt. news: item_0: title: Velkommen til demo-projektet @@ -433,24 +433,24 @@ da: options: name: At komme gang text: | - We are glad you joined! We suggest to try a few things to get started in OpenProject. + Vi er glade for, at du er med! Vi foreslår, at du prøver et par ting for at komme i gang med OpenProject. - This demo project offers roles, workflows and work packages that are specialized for BIM. + Dette demoprojekt tilbyder roller, arbejdsgange og arbejdspakker, der er specialiseret til BIM. - _Try the following steps:_ + Prøv følgende trin:_ - 1. _Invite new members to your project:_ → Go to [Members]({{opSetting:base_url}}/projects/demo-bim-project/members) in the project navigation. - 2. _Upload and view 3D-models in IFC format:_ → Go to [BCF]({{opSetting:base_url}}/projects/demo-bim-project/bcf) in the project navigation. - 3. _Create and manage BCF issues linked directly in the IFC model:_ → Go to [BCF]({{opSetting:base_url}}/projects/demo-bim-project/bcf) → Create. - 4. _View the work in your projects:_ → Go to [Work packages]({{opSetting:base_url}}/projects/demo-bim-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22bcfIssueAssociated%22%2C%22o%22%3A%22%3D%22%2C%22v%22%3A%5B%22f%22%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D) in the project navigation. - 5. _Create a new work package:_ → Go to [Work packages → Create]({{opSetting:base_url}}/projects/demo-bim-project/work_packages/new?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22bcfIssueAssociated%22%2C%22o%22%3A%22%3D%22%2C%22v%22%3A%5B%22f%22%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D&type=11). - 6. _Create and update a Gantt chart:_ → Go to [Gantt chart]({{opSetting:base_url}}/projects/demo-bim-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22assignee%22%2C%22responsible%22%5D%2C%22tv%22%3Atrue%2C%22tzl%22%3A%22weeks%22%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D) in the project navigation. - 7. _Activate further modules:_ → Go to [Project settings → Modules]({{opSetting:base_url}}/projects/demo-bim-project/settings/modules). - 8. _Check out the tile view to get an overview of your BCF issues:_ → Go to [Work packages]({{opSetting:base_url}}/projects/demo-bim-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22id%3Aasc%22%2C%22f%22%3A%5B%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22card%22%7D) - 9. _Working agile? Create a new board:_ → Go to [Boards]({{opSetting:base_url}}/projects/demo-bim-project/boards) + 1. _Invitér nye medlemmer til dit projekt:_ → Gå til [Medlemmer]({{opSetting:base_url}}/projects/demo-bim-project/members) i projektnavigationen. + 2. _Upload og se 3D-modeller i IFC-format:_ → Gå til [BCF]({{opSetting:base_url}}/projects/demo-bim-project/bcf) i projektnavigationen. + 3. _Create and manage BCF issues linked directly in the IFC model:_ → Gå til [BCF]({{opSetting:base_url}}/projects/demo-bim-project/bcf) → Create. + 4. _Se arbejdet i dine projekter:_ → Gå til [Work packages]({{opSetting:base_url}}/projects/demo-bim-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22bcfIssueAssociated%22%2C%22o%22%3A%22%3D%22%2C%22v%22%3A%5B%22f%22%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D) i projektnavigationen. + 5. _Create a new work package:_ → Gå til [Work packages → Create]({{opSetting:base_url}}/projects/demo-bim-project/work_packages/new?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22bcfIssueAssociated%22%2C%22o%22%3A%22%3D%22%2C%22v%22%3A%5B%22f%22%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D&type=11). + 6. _Create and update a Gantt chart:_ → Go to [Gantt chart]({{opSetting:base_url}}/projects/demo-bim-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22assignee%22%2C%22responsible%22%5D%2C%22tv%22%3Atrue%2C%22tzl%22%3A%22weeks%22%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D) i projektnavigationen. + 7. _Aktivér yderligere moduler:_ → Gå til [Projektindstillinger → Moduler]({{opSetting:base_url}}/projects/demo-bim-project/settings/modules). + 8. _Check out the tile view to get an overview of your BCF issues:_ → Gå til [Work packages]({{opSetting:base_url}}/projects/demo-bim-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22id%3Aasc%22%2C%22f%22%3A%5B%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22card%22%7D) + 9. _Arbejder du agilt? Opret et nyt board:_ → Gå til [Boards]({{opSetting:base_url}}/projects/demo-bim-project/boards) - Here you will find our [User Guides](https://www.openproject.org/docs/user-guide/). - Please let us know if you have any questions or need support. Contact us: [support\[at\]openproject.com](mailto:support@openproject.com). + Her finder du vores [User Guides](https://www.openproject.org/docs/user-guide/). + Lad os vide, hvis du har spørgsmål eller brug for support. Kontakt os gerne: [support\[at\]openproject.com](mailto:support@openproject.com). item_4: options: name: Medlemmer @@ -462,233 +462,233 @@ da: name: Milepæle work_packages: item_0: - subject: Project kick off creating BIM model + subject: Projektstart med oprettelse af BIM-model description: |- - The project Kickoff initializes the start of the project in your company. The whole project team should be invited to the Kickoff for a first briefing. + Projektets kick off markerer starten på projektet i din virksomhed. Alle, der er en del af projektet, bør inviteres til kick off for at få en første briefing. - The next step could be to check out the timetable and adjusting the appointments, by looking at the [Gantt chart]({{opSetting:base_url}}/projects/demo-bim-project/work_packages?query_props=%7B%22c%22%3A%5B%22id%22%2C%22subject%22%2C%22startDate%22%2C%22dueDate%22%5D%2C%22tv%22%3Atrue%2C%22tzl%22%3A%22weeks%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22status%22%2C%22o%22%3A%22o%22%2C%22v%22%3A%5B%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%7D). + Det næste skridt kunne være at tjekke tidsplanen og justere aftalerne ved at se på [Gantt-diagrammet]({{opSetting:base_url}}/projects/demo-construction-project/work_packages?query_props=%7B%22c%22%3A%5B%22id%22%2C%22subject%22%2C%22startDate%22%2C%22dueDate%22%5D%2C%22tv%22%3Atrue%2C%22tzl%22%3A%22weeks%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22status%22%2C%22o%22%3A%22o%22%2C%22v%22%3A%5B%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%7D). item_1: - subject: Project preparation - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + subject: Forberedelse af projektet + description: Denne type er hierarkisk overordnet typerne "Clash" og "Request", og repræsenterer således en generel note. children: item_0: - subject: Gathering the project specific data and information for the BIM model + subject: Indsamling af projektspecifikke data og oplysninger til BIM-modellen description: |- - ## Goal + ## Mål - * Identify the information strategy for the customer (e.g. by using plain language questions) - * If provided, analyze the customer information requirements for the BIM model - * Define an information delivery strategy according to the customers needs + * Identificer kundens informationsstrategi (f.eks. ved hjælp af spørgsmål i almindeligt sprog) + * Hvis det er muligt, analyser kundens informationskrav til BIM-modellen + * Definer en informationsleveringsstrategi i henhold til kundens behov - ## Description + ## Beskrivelse - * Analyzing the customers needs and goals for using the BIM methodology - * Results of this tasks should be: - * The requirements for the project - * A strategy for the delivery phase - * ... + * Analyser kundens behov og mål for brug af BIM-metoden + * Resultatet af denne opgave bør være: + * Kravene til projektet + * En strategi for leveringsfasen + * ... item_1: - subject: Creating the BIM execution plan + subject: Oprettelse af BIM-udførelsesplanen description: |- - # Goal + # Mål - * A BIM execution plan will be defined according to the exchange requirements specifications (ERS) - * All team members and partners have a plan on how to reach each of the project goals + * En BIM-eksekveringsplan vil blive defineret i henhold til specifikationerne for udvekslingskrav (ERS) + * Alle teammedlemmer og partnere har en plan for, hvordan de skal nå hvert af projektets mål - # Description + # Beskrivelse - * Depending on the identifies use cases, the individual Information Delivery Manuals will be defined - * To handle the technological interfaces, a software topology will be defined and analyzed and verified - * ... + * Afhængigt af de identificerede brugssager vil de individuelle informationsleveringsmanualer blive defineret + * For at håndtere de teknologiske grænseflader vil en softwaretopologi blive defineret og analyseret og verificeret + * ... item_2: - subject: Completion of the BIM execution plan - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + subject: Afslutning af BIM-udførelsesplanen + description: Denne type er hierarkisk overordnet typerne "Clash" og "Request", og repræsenterer således en generel note. item_2: - subject: End of preparation phase - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + subject: Afslutning af forberedelsesfasen + description: Denne type er hierarkisk overordnet typerne "Clash" og "Request", og repræsenterer således en generel note. item_3: - subject: Creating initial BIM model - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + subject: Oprettelse af indledende BIM-model + description: Denne type er hierarkisk overordnet typerne "Clash" og "Request", og repræsenterer således en generel note. children: item_0: - subject: Modelling initial BIM model + subject: Modellering af indledende BIM-model description: |- - # Goal + # Mål - * Modelling the initial BIM model - * Creating a BIM model for the whole project team + * Modellering af den oprindelige BIM-model + * Oprettelse af en BIM-model for hele projektteamet - # Description + # Beskrivelse - * According to the gathered data from the customer, the initial model will be modelled - * The model shall be modelled according to the LOD Matrices and contain the information needed - * ... + * Ifølge de indsamlede data fra kunden vil den oprindelige model blive modelleret + * Modellen skal modelleres i henhold til LOD-matricerne og indeholde de nødvendige oplysninger + * ... item_1: - subject: Initial, internal model check and revising + subject: Indledende, intern modelkontrol og revision description: |- - # Goal + # Mål - * Submitting a BIM model according to the defined standards + * Indsendelse af en BIM-model i henhold til de definerede standarder - # Description + # Beskrivelse - * The model shall be checked, according to the defined standards (conventions, LOD, ...) and revised - * ... + * Modellen skal kontrolleres i henhold til de definerede standarder (konventioner, LOD, ...) og revideres + * ... item_2: - subject: Submitting initial BIM model - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + subject: Indsendelse af indledende BIM-model + description: Denne type er hierarkisk overordnet typerne "Clash" og "Request", og repræsenterer således en generel note. item_4: - subject: Modelling, first cycle - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + subject: Modellering, første cyklus + description: Denne type er hierarkisk overordnet typerne "Clash" og "Request", og repræsenterer således en generel note. children: item_0: - subject: Referencing external BIM models + subject: Henvisning til eksterne BIM-modeller description: |- - # Goal + # Mål - * Having a foundation for developing the internal model/ offering answers - * Using the external model to develop the internal model + * At have et grundlag for at udvikle den interne model/ tilbyde svar + * At bruge den eksterne model til at udvikle den interne model - # Description + # Beskrivelse - * The external model will be referenced in the BIM platform, thus used for modelling the internal model - * ... + * Den eksterne model vil blive refereret i BIM-platformen og dermed brugt til modellering af den interne model + * ... item_1: - subject: Modelling the BIM model + subject: Modellering af BIM-modellen description: |- - # Goal + # Mål - * Creating a BIM model for the project - * Creating a BIM model for the whole project team + * Oprettelse af en BIM-model for projektet + * Oprettelse af en BIM-model for hele projektteamet - # Description + # Beskrivelse - * The model will be created according to the BIM execution plan - * ... + * Modellen vil blive oprettet i henhold til BIM-eksekveringsplanen + * ... item_2: - subject: First Cycle, internal model check and revising + subject: Første cyklus, intern modelkontrol og revision description: |- - # Goal + # Mål - * Submitting a BIM model according to the defined standards + * Indsendelse af en BIM-model i henhold til de definerede standarder - # Description + # Beskrivelse - * The model shall be checked, according to the defined standards (conventions, LOD, ...) and revised. - * ... + * Modellen skal kontrolleres i henhold til de definerede standarder (konventioner, LOD, ...) og revideres. + * ... item_3: - subject: Submitting BIM model - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + subject: Indsendelse af BIM-model + description: Denne type er hierarkisk overordnet typerne "Clash" og "Request", og repræsenterer således en generel note. item_5: - subject: Coordination, first cycle - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + subject: Koordinering, første cyklus + description: Denne type er hierarkisk overordnet typerne "Clash" og "Request", og repræsenterer således en generel note. children: item_0: - subject: Coordinate the different BIM models + subject: Koordiner de forskellige BIM-modeller description: |- - # Goal + # Mål - * Assemble the different BIM models of the whole project team - * Coordinate the identified issues + * Samle de forskellige BIM-modeller fra hele projektteamet + * Koordinere de identificerede problemer - # Description + # Beskrivelse - * The different BIM models will be assembled and checked - * The identified model specific issues will be communicated via BCF files - * ... + * De forskellige BIM-modeller samles og kontrolleres + * De identificerede modelspecifikke problemer kommunikeres via BCF-filer + * ... item_1: - subject: Issue management, first cycle + subject: Problemstyring, første cyklus item_2: - subject: Finishing coordination, first cycle - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + subject: Efterbehandlingskoordinering, første cyklus + description: Denne type er hierarkisk overordnet typerne "Clash" og "Request", og repræsenterer således en generel note. item_6: - subject: Modelling & coordinating, second cycle - description: "## Goal\r\n\r\n* ...\r\n\r\n## Description\r\n\r\n* \\ ..." + subject: Modellering og koordinering, anden cyklus + description: "## Mål\r\n\r\n* ...\r\n\r\n## Beskrivelse\r\n\r\n* \\ ..." item_7: - subject: Modelling & coordinating, ... cycle - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + subject: Modellering og koordinering, ... cyklus + description: Denne type er hierarkisk overordnet typerne "Clash" og "Request", og repræsenterer således en generel note. item_8: - subject: Modelling & coordinating, (n-th minus 1) cycle - description: "## Goal\r\n\r\n* ...\r\n\r\n## Description\r\n\r\n* \\ ..." + subject: Modellering og koordinering, (n-te minus 1) cyklus + description: "## Mål\r\n\r\n* ...\r\n\r\n## Beskrivelse\r\n\r\n* \\ ..." item_9: - subject: Modelling & coordinating n-th cycle - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + subject: Modellering og koordinering af n-te cyklus + description: Denne type er hierarkisk overordnet typerne "Clash" og "Request", og repræsenterer således en generel note. item_10: - subject: Finishing modelling & coordinating, n-th cycle - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + subject: Færdiggørelse af modellering og koordinering, n'te cyklus + description: Denne type er hierarkisk overordnet typerne "Clash" og "Request", og repræsenterer således en generel note. item_11: - subject: Use model for construction phase + subject: Brug modellen til byggefasen children: item_0: - subject: Handover model for construction crew + subject: Overdragelsesmodel til byggehold description: |- - ## Goal + ## Mål - * Everyone knows the model and their tasks - * Everybody gets all the relevant information, model based - * ... + * Alle kender modellen og deres opgaver + * Alle får al relevant information, modelbaseret + * ... - ## Description + ## Beskrivelse - * The Kickoff on the construction site includes an introduction to the model - * All the objects should have the information needed for the assigned tasks. If not, data enrichment of the model needs to be done - * ... + * Kickoff på byggepladsen omfatter en introduktion til modellen + * Alle objekter bør have de oplysninger, der er nødvendige for de tildelte opgaver. Hvis ikke, skal modellen beriges med data + * ... item_1: - subject: Construct the building + subject: Konstruer bygningen description: |- - ## Goal + ## Mål - * New issues realized on construction site will be handled model based - * Issues will be documented by using the BCF files and the BIM model + * Nye problemer på byggepladsen vil blive håndteret modelbaseret + * Problemer vil blive dokumenteret ved hjælp af BCF-filer og BIM-modellen - ## Description + ## Beskrivelse - * New issues will be documented using BCF files as sticky notes for the model - * The BCF files will be used to assign, track and correct issues - * ... + * Nye problemer vil blive dokumenteret ved hjælp af BCF-filer som sticky notes til modellen + * BCF-filerne vil blive brugt til at tildele, spore og rette problemer + * ... item_2: - subject: Finish construction + subject: Færdiggør konstruktion item_12: - subject: Issue management, construction phase + subject: Problemstyring, byggefase item_13: - subject: Handover for Facility Management + subject: Overdragelse til Facility Management description: |- - ## Goal + ## Mål - * The BIM model will be used for the Facility Management - * The model provides all the relevant information for commissioning and operating the building - * ... + * BIM-modellen vil blive brugt til Facility Management + * Modellen indeholder alle relevante oplysninger til idriftsættelse og drift af bygningen + * ... - ## Description + ## Beskrivelse - * The model contains the relevant information for the facility manager - * The model can be used for the operating system of the building - * ... + * Modellen indeholder de relevante oplysninger for facility manageren + * Modellen kan bruges til bygningens driftssystem + * ... item_14: - subject: Asset Management - description: Enjoy your building :) + subject: Forvaltning af aktiver + description: Nyd din bygning :) demo-bcf-management-project: - name: "(Demo) BCF management" + name: "(Demo) BCF-ledelse" status_explanation: Alle opgaver følger tidsplanen. De involverede kender deres opgaver. Systemet er fuldt opsat. - description: This is a short summary of the goals of this demo BCF management project. + description: Dette er et kort resumé af målene for dette demobyggeprojekt. ifc_models: item_0: - name: Hospital - Architecture (cc-by-sa-3.0 Autodesk Inc.) + name: Hospital - Strukturel (cc-by-sa-3.0 Autodesk Inc.) item_1: - name: Hospital - Structural (cc-by-sa-3.0 Autodesk Inc.) + name: Hospital - Strukturel (cc-by-sa-3.0 Autodesk Inc.) item_2: - name: Hospital - Mechanical (cc-by-sa-3.0 Autodesk Inc.) + name: Hospital - Mekanisk (cc-by-sa-3.0 Autodesk Inc.) categories: item_0: Kategori 1 (skal ændres i Projektindstillinger) queries: item_0: - name: Issues + name: Emner item_1: - name: Clashes + name: Konflikter item_2: - name: Requests + name: Anmodninger item_3: - name: Remarks + name: Bemærkninger item_4: name: Projektplan item_5: @@ -699,7 +699,7 @@ da: name: Teamplanlægger boards: bcf: - name: BCF issues + name: BCF-emner project-overview: widgets: item_0: @@ -709,23 +709,23 @@ da: options: name: At komme gang text: | - We are glad you joined! We suggest to try a few things to get started in OpenProject. + Vi er glade for, at du er med! Vi foreslår, at du prøver et par ting for at komme i gang med OpenProject. - This demo project shows BCF management functionalities. + Dette demoprojekt viser BCF-styringsfunktioner. - _Try the following steps:_ + Prøv følgende trin:_ - 1. _Invite new members to your project:_ → Go to [Members]({{opSetting:base_url}}/projects/demo-bcf-management-project/members?show_add_members=true) in the project navigation. - 2. _Upload and view 3D-models in IFC format:_ → Go to [BCF]({{opSetting:base_url}}/projects/demo-bim-project/bcf) in the project navigation. - 3. _Create and manage BCF issues linked directly in the IFC model:_ → Go to [BCF]({{opSetting:base_url}}/projects/demo-bim-project/bcf) → Create. - 4. _View the BCF files in your project:_ → Go to [BCF]({{opSetting:base_url}}/projects/demo-bcf-management-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22status%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22id%3Aasc%22%2C%22f%22%3A%5B%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22card%22%7D) in the project navigation. - 5. _Load your BCF files:_ → Go to [BCF → Import.]({{opSetting:base_url}}/projects/demo-bcf-management-project/issues/upload) - 6. _Create and update a Gantt chart:_ → Go to [Gantt chart]({{opSetting:base_url}}/projects/demo-bcf-management-project/work_packages?query_props=%7B%22c%22%3A%5B%22id%22%2C%22subject%22%2C%22startDate%22%2C%22dueDate%22%5D%2C%22tv%22%3Atrue%2C%22tzl%22%3A%22days%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22status%22%2C%22o%22%3A%22o%22%2C%22v%22%3A%5B%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%7D) in the project navigation. - 7. _Activate further modules:_ → Go to [Project settings → Modules.]({{opSetting:base_url}}/projects/demo-bcf-management-project/settings/modules) - 8. _You love the agile approach? Create a board:_ → Go to [Boards]({{opSetting:base_url}}/projects/demo-bcf-management-project/boards). + 1. _Invitér nye medlemmer til dit projekt:_ → Gå til [Medlemmer]({{opSetting:base_url}}/projects/demo-bcf-management-project/members?show_add_members=true) i projektnavigationen. + 2. _Upload og vis 3D-modeller i IFC-format:_ → Gå til [BCF]({{opSetting:base_url}}/projects/demo-bim-project/bcf) i projektnavigationen. + 3. _Create and manage BCF issues linked directly in the IFC model:_ → Gå til [BCF]({{opSetting:base_url}}/projects/demo-bim-project/bcf) → Create. + 4. _Vis BCF-filerne i dit projekt:_ → Gå til [BCF]({{opSetting:base_url}}/projects/demo-bcf-management-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22status%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22id%3Aasc%22%2C%22f%22%3A%5B%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22card%22%7D) i projektnavigationen. + 5. _Load dine BCF-filer:_ → Gå til [BCF → Import.]({{opSetting:base_url}}/projects/demo-bcf-management-project/issues/upload) + 6. _Create and update a Gantt chart:_ → Go to [Gantt chart]({{opSetting:base_url}}/projects/demo-bcf-management-project/work_packages?query_props=%7B%22c%22%3A%5B%22id%22%2C%22subject%22%2C%22startDate%22%2C%22dueDate%22%5D%2C%22tv%22%3Atrue%2C%22tzl%22%3A%22days%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22status%22%2C%22o%22%3A%22o%22%2C%22v%22%3A%5B%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%7D) i projektnavigationen. + 7. _Aktiver yderligere moduler:_ → Gå til [Projektindstillinger → Moduler]({{opSetting:base_url}}/projects/demo-bcf-management-project/settings/modules) + 8. _Er du vild med den agile tilgang? Opret et board:_ → Gå til [Boards]({{opSetting:base_url}}/projects/demo-bcf-management-project/boards). - Here you will find our [User Guides](https://www.openproject.org/docs/user-guide/). - Please let us know if you have any questions or need support. Contact us: [support\[at\]openproject.com](mailto:support@openproject.com). + Her finder du vores [Brugervejledninger](https://www.openproject.org/docs/user-guide/). + Lad os vide, hvis du har spørgsmål eller brug for support. Kontakt os gerne: [support\[at\]openproject.com](mailto:support@openproject.com). item_4: options: name: Medlemmer diff --git a/modules/bim/config/locales/crowdin/da.yml b/modules/bim/config/locales/crowdin/da.yml index c6f2c37015a..463ccf50982 100644 --- a/modules/bim/config/locales/crowdin/da.yml +++ b/modules/bim/config/locales/crowdin/da.yml @@ -7,137 +7,137 @@ da: label_bim: 'BIM' bcf: label_bcf: 'BCF' - label_imported_failed: 'Failed imports of BCF topics' - label_imported_successfully: 'Successfully imported BCF topics' - label_bcf_issue_associated: "BCF issue associated" - issues: "Issues" - recommended: 'recommended' - not_recommended: 'not recommended' - no_viewpoints: 'No viewpoints' + label_imported_failed: 'Mislykket import af BCF-emner' + label_imported_successfully: 'Importerede BCF-emner med succes' + label_bcf_issue_associated: "BCF-problem forbundet med" + issues: "Problemer" + recommended: 'Anbefalet' + not_recommended: 'ikke anbefalet' + no_viewpoints: 'Ingen synspunkter' new_badge: "Ny" exceptions: - file_invalid: "BCF file invalid" + file_invalid: "BCF-fil ugyldig" x_bcf_issues: - zero: 'No BCF issues' + zero: 'Ingen BCF-problemer' one: 'Et BCF- problem' other: '%{count} BCF sager' bcf_xml: - xml_file: 'BCF XML File' - import_title: 'Import' + xml_file: 'BCF XML-fil' + import_title: 'Importér' export: 'Eksportér' - import_update_comment: '(Updated in BCF import)' - import_failed: 'Cannot import BCF file: %{error}' + import_update_comment: '(Opdateret i BCF-import)' + import_failed: 'Kan ikke importere BCF-fil: %{error}' import_failed_unsupported_bcf_version: 'Kunne ikke læse BCF- filen: BCF- versionen understøttes ikke. Kontroller, at versionen er mindst %{minimal_version} eller højere.' - import_successful: 'Imported %{count} BCF issues' - import_canceled: 'BCF-XML import canceled.' - type_not_active: "The issue type is not activated for this project." + import_successful: 'Importeret %{count} BCF-problemer' + import_canceled: 'BCF-XML-import annulleret.' + type_not_active: "Problemtypen er ikke aktiveret for dette projekt." import: - num_issues_found: '%{x_bcf_issues} are contained in the BCF-XML file, their details are listed below.' - button_prepare: 'Prepare import' - button_perform_import: 'Confirm import' + num_issues_found: '%{x_bcf_issues} er indeholdt i BCF-XML-filen, deres detaljer er angivet nedenfor.' + button_prepare: 'Forbered import' + button_perform_import: 'Bekræft import' button_proceed: 'Fortsæt med import' - button_back_to_list: 'Back to list' - no_permission_to_add_members: 'You do not have sufficient permissions to add them as members to the project.' - contact_project_admin: 'Contact your project admin to add them as members and start this import again.' - continue_anyways: 'Do you want to proceed and finish the import anyways?' - description: "Provide a BCF-XML v2.1 file to import into this project. You can examine its contents before performing the import." - invalid_types_found: 'Invalid topic type names found' - invalid_statuses_found: 'Invalid status names found' - invalid_priorities_found: 'Invalid priority names found' - invalid_emails_found: 'Invalid email addresses found' - unknown_emails_found: 'Unknown email addresses found' - unknown_property: 'Unknown property' - non_members_found: 'Non project members found' - import_types_as: 'Set all these types to' - import_statuses_as: 'Set all these statuses to' - import_priorities_as: 'Set all these priorities to' - invite_as_members_with_role: 'Invite them as members to the project "%{project}" with role' - add_as_members_with_role: 'Add them as members to the project "%{project}" with role' - no_type_provided: 'No type provided' - no_status_provided: 'No status provided' - no_priority_provided: 'No priority provided' - perform_description: "Do you want to import or update the issues listed above?" - replace_with_system_user: 'Replace them with "System" user' - import_as_system_user: 'Import them as "System" user.' + button_back_to_list: 'Tilbage til liste' + no_permission_to_add_members: 'Du har ikke tilstrækkelige tilladelser til at tilføje dem som medlemmer af projektet.' + contact_project_admin: 'Kontakt din projektadministrator for at tilføje dem som medlemmer og starte denne import igen.' + continue_anyways: 'Vil du fortsætte og afslutte importen alligevel?' + description: "Angiv en BCF-XML v2.1-fil, der skal importeres til dette projekt. Du kan undersøge dens indhold, før du udfører importen." + invalid_types_found: 'Ugyldige navne på emnetyper fundet' + invalid_statuses_found: 'Ugyldige statusnavne fundet' + invalid_priorities_found: 'Ugyldige prioritetsnavne fundet' + invalid_emails_found: 'Ugyldige e-mailadresser fundet' + unknown_emails_found: 'Ukendte e-mailadresser fundet' + unknown_property: 'Ukendt egenskab' + non_members_found: 'Ikke-projektmedlemmer fundet' + import_types_as: 'Sæt alle disse typer til' + import_statuses_as: 'Sæt alle disse statusser til' + import_priorities_as: 'Sæt alle disse prioriteter til' + invite_as_members_with_role: 'Inviter dem som medlemmer til projektet "%{project}" med rollen' + add_as_members_with_role: 'Tilføj dem som medlemmer til projektet "%{project}" med rollen' + no_type_provided: 'Ingen type angivet' + no_status_provided: 'Ingen status angivet' + no_priority_provided: 'Ingen prioritet angivet' + perform_description: "Vil du importere eller opdatere de problemer, der er nævnt ovenfor?" + replace_with_system_user: 'Erstat dem med "System"-brugeren' + import_as_system_user: 'Importer dem som "System"-bruger.' what_to_do: "Hvad vil du lave?" - work_package_has_newer_changes: "Outdated! This topic was not updated as the latest changes on the server were newer than the \"ModifiedDate\" of the imported topic. However, comments to the topic were imported." + work_package_has_newer_changes: "Forældet! Dette emne blev ikke opdateret, da de seneste ændringer på serveren var nyere end \"ModifiedDate\" for det importerede emne. Kommentarer til emnet blev dog importeret." bcf_file_not_found: "Kunne ikke finde BCF- fil. Start venligst upload processen igen." export: format: bcf: "BCF-XML" attributes: - bcf_thumbnail: "BCF snapshot" + bcf_thumbnail: "BCF øjebliksbillede" project_module_bcf: "BCF" project_module_bim: "BCF" - permission_view_linked_issues: "View BCF issues" - permission_manage_bcf: "Import and manage BCF issues" + permission_view_linked_issues: "Vis BCF-problemer" + permission_manage_bcf: "Import og administrer BCF-problemer" permission_delete_bcf: "Slet BCF sager" oauth: scopes: - bcf_v2_1: "Full access to the BCF v2.1 API" - bcf_v2_1_text: "Application will receive full read & write access to the OpenProject BCF API v2.1 to perform actions on your behalf." + bcf_v2_1: "Fuld adgang til BCF v2.1 API'en" + bcf_v2_1_text: "Applikationen får fuld læse- og skriveadgang til OpenProject BCF API v2.1 for at udføre handlinger på dine vegne." activerecord: models: bim/ifc_models/ifc_model: "IFC-model" attributes: bim/ifc_models/ifc_model: - ifc_attachment: "IFC file" - is_default: "Default model" - attachments: "IFC file" + ifc_attachment: "IFC-fil" + is_default: "Standardmodel" + attachments: "IFC-fil" bim/bcf/issue: - bcf_comment: "BCF comment" - bim_snippet: "BIM snippet" - reference_links: "Reference links" - stage: "Stage" + bcf_comment: "BCF-kommentar" + bim_snippet: "BIM-uddrag" + reference_links: "Referencelinks" + stage: "Fase" viewpoint: "Synsvinkel" errors: models: bim/ifc_models/ifc_model: attributes: base: - ifc_attachment_missing: "No ifc file attached." - invalid_ifc_file: "The provided file is not a valid IFC file." + ifc_attachment_missing: "Ingen ifc-fil vedhæftet." + invalid_ifc_file: "Den angivne fil er ikke en gyldig IFC-fil." bim/bcf/viewpoint: - bitmaps_not_writable: "bitmaps is not writable as it is not yet implemented." - index_not_integer: "index is not an integer." - invalid_clipping_planes: "clipping_planes is invalid." - invalid_components: "components is invalid." - invalid_lines: "lines is invalid." - invalid_orthogonal_camera: "orthogonal_camera is invalid." - invalid_perspective_camera: "perspective_camera is invalid." - mismatching_guid: "The guid in the json_viewpoint does not match the model's guid." - no_json: "Is not a well structured json." - snapshot_type_unsupported: "snapshot_type needs to be either 'png' or 'jpg'." - snapshot_data_blank: "snapshot_data needs to be provided." - unsupported_key: "An unsupported json property is included." + bitmaps_not_writable: "bitmaps er ikke skrivbar, da den endnu ikke er implementeret." + index_not_integer: "index er ikke et heltal." + invalid_clipping_planes: "clipping_planes er ugyldig." + invalid_components: "komponenter er ugyldige." + invalid_lines: "linjer er ugyldige." + invalid_orthogonal_camera: "orthogonal_camera er ugyldig." + invalid_perspective_camera: "perspective_camera er ugyldig." + mismatching_guid: "Guid i json_viewpoint matcher ikke modellens guid." + no_json: "Er ikke en velstruktureret json." + snapshot_type_unsupported: "snapshot_type skal være enten 'png' eller 'jpg'." + snapshot_data_blank: "snapshot_data skal angives." + unsupported_key: "En ikke-understøttet json-egenskab er inkluderet." bim/bcf/issue: uuid_already_taken: "Kan ikke importere dette BCF problem, da der allerede er en anden med den samme GUID. Kunne det være, at dette BCF-problem allerede var blevet importeret til et andet projekt?" ifc_models: - label_ifc_models: 'IFC models' - label_new_ifc_model: 'New IFC model' + label_ifc_models: 'IFC-modeller' + label_new_ifc_model: 'Ny IFC-model' label_show_defaults: 'Vis standarder' - label_default_ifc_models: 'Default IFC models' + label_default_ifc_models: 'Standard IFC-modeller' label_edit_defaults: 'Rediger standarder' no_defaults_warning: - title: 'No IFC model was set as default for this project.' - check_1: 'Check that you have uploaded at least one IFC model.' - check_2: 'Check that at least one IFC model is set to "Default".' - no_results: "No IFC models have been uploaded in this project." + title: 'Ingen IFC-model blev indstillet som standard for dette projekt.' + check_1: 'Tjek, at du har uploadet mindst én IFC-model.' + check_2: 'Kontroller, at mindst én IFC-model er indstillet til "Standard".' + no_results: "Der er ikke uploadet nogen IFC-modeller i dette projekt." conversion_status: - label: 'Processing?' - pending: 'Pending' - processing: 'Processing' - completed: 'Completed' + label: 'Bearbejder' + pending: 'Afventer' + processing: 'Bearbejder' + completed: 'Udført' error: 'Fejl' processing_notice: - processing_default: 'The following default IFC models are still being processed and are thus not available, yet:' + processing_default: 'Følgende standard IFC-modeller er stadig under behandling og er derfor ikke tilgængelige endnu:' flash_messages: - upload_successful: 'Upload succeeded. It will now get processed and will be ready to use in a couple of minutes.' + upload_successful: 'Uploaden lykkedes. Den vil nu blive behandlet og være klar til brug om et par minutter.' conversion: - missing_commands: "The following IFC converter commands are missing on this system: %{names}" - project_module_ifc_models: "IFC models" - permission_view_ifc_models: "View IFC models" - permission_manage_ifc_models: "Import and manage IFC models" + missing_commands: "Følgende IFC-konverterkommandoer mangler på dette system: %{names}" + project_module_ifc_models: "IFC-modeller" + permission_view_ifc_models: "Se IFC-modeller" + permission_manage_ifc_models: "Importer og administrer IFC-modeller" extraction: available: - ifc_convert: "IFC conversion pipeline available" + ifc_convert: "IFC-konverteringspipeline tilgængelig" diff --git a/modules/bim/config/locales/crowdin/fr.yml b/modules/bim/config/locales/crowdin/fr.yml index 717608ff597..288904f98e5 100644 --- a/modules/bim/config/locales/crowdin/fr.yml +++ b/modules/bim/config/locales/crowdin/fr.yml @@ -59,7 +59,7 @@ fr: perform_description: "Voulez-vous importer ou mettre à jour les problèmes repris ci-dessus ?" replace_with_system_user: 'Les remplacer par l''utilisateur "Système"' import_as_system_user: 'Les importer comme utilisateur "Système".' - what_to_do: "Que voulez-vous faire ?" + what_to_do: "Que voulez-vous faire?" work_package_has_newer_changes: "Obsolète ! Ce sujet n'a pas été mis à jour, car les derniers changements sur le serveur étaient plus récents que la \"ModifiedDate\" du sujet importé. Toutefois, les commentaires sur le sujet ont été importés." bcf_file_not_found: "Impossible de localiser le fichier BCF. Veuillez recommencer le processus de téléversement." export: diff --git a/modules/budgets/config/locales/crowdin/cs.yml b/modules/budgets/config/locales/crowdin/cs.yml index d521d47cb54..250c5640e15 100644 --- a/modules/budgets/config/locales/crowdin/cs.yml +++ b/modules/budgets/config/locales/crowdin/cs.yml @@ -27,7 +27,7 @@ cs: budget: author: "Autor" available: "Dostupné" - budget: "Plánované" + budget: "Rozpočet" budget_ratio: "Stráveno (poměr)" description: "Popis" spent: "Strávený čas" diff --git a/modules/costs/config/locales/crowdin/ja.yml b/modules/costs/config/locales/crowdin/ja.yml index a671d39aeab..015103d844e 100644 --- a/modules/costs/config/locales/crowdin/ja.yml +++ b/modules/costs/config/locales/crowdin/ja.yml @@ -205,7 +205,7 @@ ja: setting_enforce_tracking_start_and_end_times: "開始/終了時間を必須とする" setting_enforce_without_allow: "開始時間と終了時間を要求することは許可されていないとできません" setting_allow_tracking_start_and_end_times_caption: "時間を記録する際に、開始時間と終了時間を入力できるようにする。" - setting_enforce_tracking_start_and_end_times_caption: "時間を記録する際、開始時間と終了時間の入力を必須にします。" + setting_enforce_tracking_start_and_end_times_caption: "時間を記録する際、開始時間と終了時間の入力が必須となる。" text_assign_time_and_cost_entries_to_project: "報告された時間とコストをプロジェクトに割り当てる" text_destroy_cost_entries_question: "削除しようとしているワークパッケージが%{cost_entries} 件報告されました。どうしますか?" text_destroy_time_and_cost_entries: "報告された時間とコストを削除する" diff --git a/modules/gitlab_integration/config/locales/crowdin/da.yml b/modules/gitlab_integration/config/locales/crowdin/da.yml index d2f2cc5c996..24555dbb170 100644 --- a/modules/gitlab_integration/config/locales/crowdin/da.yml +++ b/modules/gitlab_integration/config/locales/crowdin/da.yml @@ -82,7 +82,7 @@ da: issue_closed_referenced_comment: > **Issue Closed:** Issue %{issue_number} [%{issue_title}](%{issue_url}) for [%{repository}](%{repository_url}) has been closed by [%{gitlab_user}](%{gitlab_user_url}). issue_reopened_referenced_comment: > - **Issue Reopened:** Issue %{issue_number} [%{issue_title}](%{issue_url}) for [%{repository}](%{repository_url}) has been reopened by [%{gitlab_user}](%{gitlab_user_url}). + **Emne Reopens:** Issue %{issue_number} [%{issue_title}](%{issue_url}) for [%{repository}](%{repository_url}) er blevet genåbnet af [%{gitlab_user}](%{gitlab_user_url}). push_single_commit_comment: > **Pushed in MR:** [%{gitlab_user}](%{gitlab_user_url}) pushed [%{commit_number}](%{commit_url}) to [%{repository}](%{repository_url}) at %{commit_timestamp}: %{commit_note} @@ -90,5 +90,5 @@ da: **Pushed in %{reference}:** [%{gitlab_user}](%{gitlab_user_url}) pushed [%{commit_number}](%{commit_url}) to [%{repository}](%{repository_url}) at %{commit_timestamp}: %{commit_note} push_multiple_commits_comment: > - **Pushed in MR:** [%{gitlab_user}](%{gitlab_user_url}) pushed multiple commits [%{commit_number}](%{commit_url}) to [%{repository}](%{repository_url}) at %{commit_timestamp}: + **Pushed in MR:** [%{gitlab_user}](%{gitlab_user_url}) pushed [%{commit_number}](%{commit_url}) to [%{repository}](%{repository_url}) at %{commit_timestamp}: %{commit_note} diff --git a/modules/gitlab_integration/config/locales/crowdin/rw.yml b/modules/gitlab_integration/config/locales/crowdin/rw.yml index 4b58a358650..a5ae5154287 100644 --- a/modules/gitlab_integration/config/locales/crowdin/rw.yml +++ b/modules/gitlab_integration/config/locales/crowdin/rw.yml @@ -60,17 +60,23 @@ rw: merge_request_reopened_comment: > **MR Reopened:** Merge request %{mr_number} [%{mr_title}](%{mr_url}) for [%{repository}](%{repository_url}) has been reopened by [%{gitlab_user}](%{gitlab_user_url}). note_commit_referenced_comment: > - **Referenced in Commit:** [%{gitlab_user}](%{gitlab_user_url}) referenced this WP in a Commit Note [%{commit_id}](%{commit_url}) on [%{repository}](%{repository_url}): %{commit_note} + **Referenced in Commit:** [%{gitlab_user}](%{gitlab_user_url}) referenced this WP in a Commit Note [%{commit_id}](%{commit_url}) on [%{repository}](%{repository_url}): + %{commit_note} note_mr_referenced_comment: > - **Referenced in MR:** [%{gitlab_user}](%{gitlab_user_url}) referenced this WP in Merge Request %{mr_number} [%{mr_title}](%{mr_url}) on [%{repository}](%{repository_url}): %{mr_note} + **Referenced in MR:** [%{gitlab_user}](%{gitlab_user_url}) referenced this WP in Merge Request %{mr_number} [%{mr_title}](%{mr_url}) on [%{repository}](%{repository_url}): + %{mr_note} note_mr_commented_comment: > - **Commented in MR:** [%{gitlab_user}](%{gitlab_user_url}) commented this WP in Merge Request %{mr_number} [%{mr_title}](%{mr_url}) on [%{repository}](%{repository_url}): %{mr_note} + **Commented in MR:** [%{gitlab_user}](%{gitlab_user_url}) commented this WP in Merge Request %{mr_number} [%{mr_title}](%{mr_url}) on [%{repository}](%{repository_url}): + %{mr_note} note_issue_referenced_comment: > - **Referenced in Issue:** [%{gitlab_user}](%{gitlab_user_url}) referenced this WP in Issue %{issue_number} [%{issue_title}](%{issue_url}) on [%{repository}](%{repository_url}): %{issue_note} + **Referenced in Issue:** [%{gitlab_user}](%{gitlab_user_url}) referenced this WP in Issue %{issue_number} [%{issue_title}](%{issue_url}) on [%{repository}](%{repository_url}): + %{issue_note} note_issue_commented_comment: > - **Commented in Issue:** [%{gitlab_user}](%{gitlab_user_url}) commented this WP in Issue %{issue_number} [%{issue_title}](%{issue_url}) on [%{repository}](%{repository_url}): %{issue_note} + **Commented in Issue:** [%{gitlab_user}](%{gitlab_user_url}) commented this WP in Issue %{issue_number} [%{issue_title}](%{issue_url}) on [%{repository}](%{repository_url}): + %{issue_note} note_snippet_referenced_comment: > - **Referenced in Snippet:** [%{gitlab_user}](%{gitlab_user_url}) referenced this WP in Snippet %{snippet_number} [%{snippet_title}](%{snippet_url}) on [%{repository}](%{repository_url}): %{snippet_note} + **Referenced in Snippet:** [%{gitlab_user}](%{gitlab_user_url}) referenced this WP in Snippet %{snippet_number} [%{snippet_title}](%{snippet_url}) on [%{repository}](%{repository_url}): + %{snippet_note} issue_opened_referenced_comment: > **Issue Opened:** Issue %{issue_number} [%{issue_title}](%{issue_url}) for [%{repository}](%{repository_url}) has been opened by [%{gitlab_user}](%{gitlab_user_url}). issue_closed_referenced_comment: > @@ -78,8 +84,11 @@ rw: issue_reopened_referenced_comment: > **Issue Reopened:** Issue %{issue_number} [%{issue_title}](%{issue_url}) for [%{repository}](%{repository_url}) has been reopened by [%{gitlab_user}](%{gitlab_user_url}). push_single_commit_comment: > - **Pushed in MR:** [%{gitlab_user}](%{gitlab_user_url}) pushed [%{commit_number}](%{commit_url}) to [%{repository}](%{repository_url}) at %{commit_timestamp}: %{commit_note} + **Pushed in MR:** [%{gitlab_user}](%{gitlab_user_url}) pushed [%{commit_number}](%{commit_url}) to [%{repository}](%{repository_url}) at %{commit_timestamp}: + %{commit_note} push_single_commit_comment_with_ref: > - **Pushed in %{reference}:** [%{gitlab_user}](%{gitlab_user_url}) pushed [%{commit_number}](%{commit_url}) to [%{repository}](%{repository_url}) at %{commit_timestamp}: %{commit_note} + **Pushed in %{reference}:** [%{gitlab_user}](%{gitlab_user_url}) pushed [%{commit_number}](%{commit_url}) to [%{repository}](%{repository_url}) at %{commit_timestamp}: + %{commit_note} push_multiple_commits_comment: > - **Pushed in MR:** [%{gitlab_user}](%{gitlab_user_url}) pushed multiple commits [%{commit_number}](%{commit_url}) to [%{repository}](%{repository_url}) at %{commit_timestamp}: %{commit_note} + **Pushed in MR:** [%{gitlab_user}](%{gitlab_user_url}) pushed multiple commits [%{commit_number}](%{commit_url}) to [%{repository}](%{repository_url}) at %{commit_timestamp}: + %{commit_note} diff --git a/modules/gitlab_integration/config/locales/crowdin/uz.yml b/modules/gitlab_integration/config/locales/crowdin/uz.yml index 8159949474f..26da96002b9 100644 --- a/modules/gitlab_integration/config/locales/crowdin/uz.yml +++ b/modules/gitlab_integration/config/locales/crowdin/uz.yml @@ -60,17 +60,23 @@ uz: merge_request_reopened_comment: > **MR Reopened:** Merge request %{mr_number} [%{mr_title}](%{mr_url}) for [%{repository}](%{repository_url}) has been reopened by [%{gitlab_user}](%{gitlab_user_url}). note_commit_referenced_comment: > - **Referenced in Commit:** [%{gitlab_user}](%{gitlab_user_url}) referenced this WP in a Commit Note [%{commit_id}](%{commit_url}) on [%{repository}](%{repository_url}): %{commit_note} + **Referenced in Commit:** [%{gitlab_user}](%{gitlab_user_url}) referenced this WP in a Commit Note [%{commit_id}](%{commit_url}) on [%{repository}](%{repository_url}): + %{commit_note} note_mr_referenced_comment: > - **Referenced in MR:** [%{gitlab_user}](%{gitlab_user_url}) referenced this WP in Merge Request %{mr_number} [%{mr_title}](%{mr_url}) on [%{repository}](%{repository_url}): %{mr_note} + **Referenced in MR:** [%{gitlab_user}](%{gitlab_user_url}) referenced this WP in Merge Request %{mr_number} [%{mr_title}](%{mr_url}) on [%{repository}](%{repository_url}): + %{mr_note} note_mr_commented_comment: > - **Commented in MR:** [%{gitlab_user}](%{gitlab_user_url}) commented this WP in Merge Request %{mr_number} [%{mr_title}](%{mr_url}) on [%{repository}](%{repository_url}): %{mr_note} + **Commented in MR:** [%{gitlab_user}](%{gitlab_user_url}) commented this WP in Merge Request %{mr_number} [%{mr_title}](%{mr_url}) on [%{repository}](%{repository_url}): + %{mr_note} note_issue_referenced_comment: > - **Referenced in Issue:** [%{gitlab_user}](%{gitlab_user_url}) referenced this WP in Issue %{issue_number} [%{issue_title}](%{issue_url}) on [%{repository}](%{repository_url}): %{issue_note} + **Referenced in Issue:** [%{gitlab_user}](%{gitlab_user_url}) referenced this WP in Issue %{issue_number} [%{issue_title}](%{issue_url}) on [%{repository}](%{repository_url}): + %{issue_note} note_issue_commented_comment: > - **Commented in Issue:** [%{gitlab_user}](%{gitlab_user_url}) commented this WP in Issue %{issue_number} [%{issue_title}](%{issue_url}) on [%{repository}](%{repository_url}): %{issue_note} + **Commented in Issue:** [%{gitlab_user}](%{gitlab_user_url}) commented this WP in Issue %{issue_number} [%{issue_title}](%{issue_url}) on [%{repository}](%{repository_url}): + %{issue_note} note_snippet_referenced_comment: > - **Referenced in Snippet:** [%{gitlab_user}](%{gitlab_user_url}) referenced this WP in Snippet %{snippet_number} [%{snippet_title}](%{snippet_url}) on [%{repository}](%{repository_url}): %{snippet_note} + **Referenced in Snippet:** [%{gitlab_user}](%{gitlab_user_url}) referenced this WP in Snippet %{snippet_number} [%{snippet_title}](%{snippet_url}) on [%{repository}](%{repository_url}): + %{snippet_note} issue_opened_referenced_comment: > **Issue Opened:** Issue %{issue_number} [%{issue_title}](%{issue_url}) for [%{repository}](%{repository_url}) has been opened by [%{gitlab_user}](%{gitlab_user_url}). issue_closed_referenced_comment: > @@ -78,8 +84,11 @@ uz: issue_reopened_referenced_comment: > **Issue Reopened:** Issue %{issue_number} [%{issue_title}](%{issue_url}) for [%{repository}](%{repository_url}) has been reopened by [%{gitlab_user}](%{gitlab_user_url}). push_single_commit_comment: > - **Pushed in MR:** [%{gitlab_user}](%{gitlab_user_url}) pushed [%{commit_number}](%{commit_url}) to [%{repository}](%{repository_url}) at %{commit_timestamp}: %{commit_note} + **Pushed in MR:** [%{gitlab_user}](%{gitlab_user_url}) pushed [%{commit_number}](%{commit_url}) to [%{repository}](%{repository_url}) at %{commit_timestamp}: + %{commit_note} push_single_commit_comment_with_ref: > - **Pushed in %{reference}:** [%{gitlab_user}](%{gitlab_user_url}) pushed [%{commit_number}](%{commit_url}) to [%{repository}](%{repository_url}) at %{commit_timestamp}: %{commit_note} + **Pushed in %{reference}:** [%{gitlab_user}](%{gitlab_user_url}) pushed [%{commit_number}](%{commit_url}) to [%{repository}](%{repository_url}) at %{commit_timestamp}: + %{commit_note} push_multiple_commits_comment: > - **Pushed in MR:** [%{gitlab_user}](%{gitlab_user_url}) pushed multiple commits [%{commit_number}](%{commit_url}) to [%{repository}](%{repository_url}) at %{commit_timestamp}: %{commit_note} + **Pushed in MR:** [%{gitlab_user}](%{gitlab_user_url}) pushed multiple commits [%{commit_number}](%{commit_url}) to [%{repository}](%{repository_url}) at %{commit_timestamp}: + %{commit_note} diff --git a/modules/ldap_groups/config/locales/crowdin/zh-CN.yml b/modules/ldap_groups/config/locales/crowdin/zh-CN.yml index cec35d2ee7b..de9f185509c 100644 --- a/modules/ldap_groups/config/locales/crowdin/zh-CN.yml +++ b/modules/ldap_groups/config/locales/crowdin/zh-CN.yml @@ -6,7 +6,7 @@ zh-CN: description: '与 OpenProject 组同步 LDAP 组以管理用户,更改他们的权限以便不同组的用户管理。' plugin_openproject_ldap_groups: name: "OpenProject LDAP 组" - description: "LDAP组成员同步。" + description: "LDAP 组成员同步。" activerecord: attributes: ldap_groups/synchronized_group: diff --git a/modules/ldap_groups/config/locales/crowdin/zh-TW.yml b/modules/ldap_groups/config/locales/crowdin/zh-TW.yml index 1a4720ebcaf..15ce71c0dc6 100644 --- a/modules/ldap_groups/config/locales/crowdin/zh-TW.yml +++ b/modules/ldap_groups/config/locales/crowdin/zh-TW.yml @@ -15,7 +15,7 @@ zh-TW: ldap_auth_source: 'LDAP 連線' sync_users: '同步使用者' ldap_groups/synchronized_filter: - filter_string: 'LDAP篩選條件' + filter_string: '簡約登入目錄制約(LDAP)篩選' auth_source: '驗證來源' ldap_auth_source: 'LDAP 連線' group_name_attribute: "群組名字屬性" diff --git a/modules/meeting/config/locales/crowdin/cs.yml b/modules/meeting/config/locales/crowdin/cs.yml index 05c059e4609..9b645ea3237 100644 --- a/modules/meeting/config/locales/crowdin/cs.yml +++ b/modules/meeting/config/locales/crowdin/cs.yml @@ -489,7 +489,7 @@ cs: notice_meeting_updated: "Tato stránka byla aktualizována někým jiným. Pro zobrazení změn znovu načtena." permission_create_meetings: "Vytvořit schůzku\n" permission_edit_meetings: "Upravit schůzku" - permission_delete_meetings: "Odstranit schůzky" + permission_delete_meetings: "Smazat schůzku" permission_view_meetings: "Zobrazit schůzky" permission_manage_agendas: "Správa zápisů" permission_manage_agendas_explanation: "Allows creating, editing and removing agenda items" diff --git a/modules/meeting/config/locales/crowdin/ja.yml b/modules/meeting/config/locales/crowdin/ja.yml index 0ff7b31ab9c..9f48cce4bbb 100644 --- a/modules/meeting/config/locales/crowdin/ja.yml +++ b/modules/meeting/config/locales/crowdin/ja.yml @@ -227,9 +227,9 @@ ja: header_occurrence: "キャンセル: ミーティング発生'%{title}'" header_series: "キャンセル: ミーティングシリーズ '%{title}'" summary_occurrence: "%{title}'の発生は %{actor}によってキャンセルされました。" - summary_series: "ミーティングシリーズ '%{title}' は %{actor}によりキャンセルされました。" - summary: "'%{title}' は %{actor}によってキャンセルされた。" - date_time: "予定日時" + summary_series: "ミーティングシリーズ '%{title}' は、 %{actor} によってキャンセルされました。" + summary: "'%{title}' は %{actor} によってキャンセルされました。" + date_time: "スケジュールされた日時" participant_added: header: "Meeting '%{title}' - Participant added" header_series: "Meeting series '%{title}' - Participant added" @@ -271,7 +271,7 @@ ja: title: "ミーティングのキャンセル" heading: "このミーティングをキャンセルしますか?" confirmation_message_html: > - テンプレートにない会議情報は失われます。 続行しますか? + テンプレートにない会議情報は失われます。 続けますか? confirm_button: "発生をキャンセル" blankslate: title: "表示するミーティングがありません" @@ -463,7 +463,7 @@ ja: confirm_button: "この予定をキャンセル" end_series_dialog: title: "一連の会議を終了" - notice_successful_notification: "参加者全員にカレンダー更新の電子メールを送信" + notice_successful_notification: "すべての出席者にカレンダーの更新をメールしました" notice_timezone_missing: タイムゾーンが設定されていない場合、%{zone} が使用されます。タイムゾーンを選択するには、ここをクリックしてください。 notice_meeting_updated: "このページは他の誰かによって更新されました。変更を表示するには再読み込みしてください。" permission_create_meetings: "会議を作成" @@ -548,7 +548,7 @@ ja: このバックログは、このワンタイムミーティングに固有のものです.アイテムをドラッグして追加またはミーティングの議題から削除することができます. label_agenda_backlog_clear_title: "議題のバックログをクリアしますか?" text_agenda_backlog_clear_description: > - 現在アジェンダバックログにあるすべての項目を削除してもよろしいですか?このアクションは元に戻せません。 + 議題のバックログ内のすべての項目を削除してもよろしいですか?この操作は取り消せません。 label_series_backlog: "シリーズバックログ" text_series_backlog: > バックログはこのシリーズのすべての出現と共有されます。 項目をドラッグして、特定のミーティングから項目を追加または削除できます。 @@ -580,7 +580,7 @@ ja: text_meeting_closed_description: "この会議は終了しています。これ以上、議題項目の追加/削除はできません。" text_meeting_in_progress_description: "議題を変更したり、各項目のアウトカムを記録したり、参加者の出席を追跡することができます。 ミーティングが完了すると、ミーティングをクローズとしてマークしてロックできます。" text_meeting_open_dropdown_description: "既存の結果は残りますが、ユーザーは新しい結果を追加することはできません。" - text_meeting_in_progress_dropdown_description: "会議中に必要な情報や決定事項などの成果を文書化する。" + text_meeting_in_progress_dropdown_description: "会議中に取られた情報のニーズや意思決定などの成果を記録します。" text_meeting_closed_dropdown_description: "この会議は終了しました。これ以上、議題や結果を変更することはできません。" text_meeting_draft_banner: "現在下書きモードです。 ミーティングの詳細を変更したり出席者を追加/削除したりしても,このミーティングはカレンダーの更新や招待状を送信しません。" text_exit_draft_mode_dialog_title: "このミーティングを開いて招待を送信しますか?" diff --git a/modules/reporting/config/locales/crowdin/ro.yml b/modules/reporting/config/locales/crowdin/ro.yml index c113cc4a5d4..ddd90658fff 100644 --- a/modules/reporting/config/locales/crowdin/ro.yml +++ b/modules/reporting/config/locales/crowdin/ro.yml @@ -70,7 +70,7 @@ ro: label_filter: "Filtrează" label_filter_add: "Adaugă filtru" label_filter_plural: "Filtre" - label_group_by: "Grupează după" + label_group_by: "Grupare după" label_group_by_add: "Adaugă atributul Grupează-după" label_inactive: "Inactiv" label_no: "Nu" diff --git a/modules/reporting/config/locales/crowdin/zh-TW.yml b/modules/reporting/config/locales/crowdin/zh-TW.yml index 6a0980310d3..014919fb7f9 100644 --- a/modules/reporting/config/locales/crowdin/zh-TW.yml +++ b/modules/reporting/config/locales/crowdin/zh-TW.yml @@ -53,7 +53,7 @@ zh-TW: label_money: "金額" label_month_reporting: "月" label_new_report: "新建成本報表" - label_open: "開啟" + label_open: "開啟中" label_operator: "操作員" label_private_report_plural: "私密成本報告" label_progress_bar_explanation: "產生報告中..." @@ -70,7 +70,7 @@ zh-TW: label_filter: "篩選條件" label_filter_add: "新增篩選條件" label_filter_plural: "篩選條件" - label_group_by: "分類" + label_group_by: "分組依據" label_group_by_add: "新增群組欄位" label_inactive: "«不活動»" label_no: "否" diff --git a/modules/storages/config/locales/crowdin/ja.yml b/modules/storages/config/locales/crowdin/ja.yml index b9f668c1fb2..51e942147b9 100644 --- a/modules/storages/config/locales/crowdin/ja.yml +++ b/modules/storages/config/locales/crowdin/ja.yml @@ -18,7 +18,7 @@ ja: token_exchange_scope: ストレージスコープ storages/project_storage: project_folder: プロジェクトフォルダ - project_folder_mode: プロジェクトフォルダーモード + project_folder_mode: プロジェクトフォルダモード storage: ストレージ storage_url: ストレージURL storages/sharepoint_storage: @@ -29,53 +29,53 @@ ja: storages/storage: authentication_method: 認証方法 creator: 作成者 - drive: ドライブID + drive: ドライブ ID host: ホスト name: 名称 password: アプリケーションのパスワード - provider_type: プロバイダー・タイプ - tenant: ディレクトリ(テナント)ID + provider_type: プロバイダーの種類 + tenant: ディレクトリ (テナント) ID errors: messages: invalid_host_url: は有効な URL ではありません。 - invalid_sharepoint_url: は有効なSharePointサイト、ライブラリ、ドキュメントのURLではありません。 - not_linked_to_project: はプロジェクトにリンクされていない。 + invalid_sharepoint_url: は有効なSharePointサイト、ライブラリ、またはドキュメントのURLではありません。 + not_linked_to_project: はプロジェクトにリンクされていません。 models: storages/file_link: attributes: origin_id: - only_numeric_or_uuid: には数値かuuidしか指定できない。 + only_numeric_or_uuid: は数値またはuuidのみとなります。 storages/project_storage: attributes: project_folder_id: blank: フォルダーを選択してください。 project_folder_mode: - mode_unavailable: はこのストレージでは使用できない。 + mode_unavailable: このストレージでは使用できません。 project_ids: blank: プロジェクトを選択してください。 storages/storage: attributes: host: - authorization_header_missing: が完全にセットアップされていません。APIリクエストのベアラートークンベースの認証に必要な "Authorization "ヘッダーをNextcloudインスタンスが受け取っていません。HTTPサーバーの設定を再度ご確認ください。 - cannot_be_connected_to: に到達できませんでした。ホストに到達可能で、OpenProject 統合アプリがインストールされていることを確認してください。 - minimal_nextcloud_version_unmet: 最小バージョン要件を満たしていない(Nextcloud 23以上である必要があります。) - not_nextcloud_server: はNextcloudサーバーではありません。 - op_application_not_installed: は、アプリ「OpenProject integration」がインストールされていないようです。インストールしてからもう一度お試しください。 + authorization_header_missing: 完全には設定されていません。 Nextcloudインスタンスは、APIリクエストのベアラートークンベースの認可に必要な「Authorization」ヘッダーを受け取りません。 HTTPサーバーの設定を再確認してください。 + cannot_be_connected_to: に到達できませんでした。ホストが到達可能で、OpenProject 統合アプリがインストールされていることを確認してください。 + minimal_nextcloud_version_unmet: 最小バージョン要件を満たしていません(Nextcloud23以上でなければなりません) + not_nextcloud_server: はNextcloudサーバーではありません + op_application_not_installed: アプリ「OpenProject統合」がインストールされていません。最初にインストールしてからもう一度お試しください。 password: - invalid_password: は無効である。 + invalid_password: は無効です。 unknown_error: could not be validated with the file storage provider. Please verify that the connection is functioning properly. models: file_link: ファイル storages/storage: ストレージ api_v3: errors: - too_many_elements_created_at_once: 一度に作成される要素が多すぎる。最大でも %{max} 、 %{actual}。 + too_many_elements_created_at_once: 一度に作成された要素が多すぎます。 %{max} の期待値は %{actual} です。 external_file_storages: 外部ファイルストレージ permission_create_files: '自動的に管理されたプロジェクトフォルダ: ファイルの作成' permission_create_files_explanation: この権限はNextcloudストレージでのみ利用できます permission_delete_files: '自動的に管理されたプロジェクトフォルダ: ファイルの削除' permission_delete_files_explanation: この権限はNextcloudストレージでのみ利用できます - permission_header_for_project_module_storages: 自動的に管理されるプロジェクトフォルダ + permission_header_for_project_module_storages: 自動的に管理されたプロジェクトフォルダ permission_manage_file_links: ファイルへのリンク管理 permission_manage_files_in_project: プロジェクト内のファイル管理 permission_read_files: '自動的に管理されたプロジェクトフォルダ: ファイルの読み込み' @@ -86,16 +86,16 @@ ja: project_module_storages: ファイルを添付する project_storages: edit_project_folder: - label: プロジェクトフォルダの編集 + label: プロジェクトフォルダを編集 open: - contact_admin: このエラーを解決するには、管理者に連絡してください。 - remote_identity_error: ストレージへの接続中に予期せぬエラーが発生しました。 + contact_admin: このエラーを解決するには管理者に問い合わせてください。 + remote_identity_error: ストレージへの接続中に予期しないエラーが発生しました。 project_folder_mode: - automatic: 自動的に管理される - inactive: 特定のフォルダなし + automatic: 自動的に管理 + inactive: 特定のフォルダがありません manual: 既存のフォルダを手動で管理 remove_project: - deletion_failure_flash: ストレージからのプロジェクトの削除に失敗しました。 %{error} + deletion_failure_flash: プロジェクトをストレージから削除できませんでした。 %{error} label: プロジェクトを削除 services: attributes: @@ -110,7 +110,7 @@ ja: one_drive_sync_service: create_folder: 'プロジェクトフォルダの作成を管理:' ensure_root_folder_permissions: 'ベースフォルダの権限を設定:' - hide_inactive_folders: '非アクティブフォルダを隠す ステップ:' + hide_inactive_folders: '非アクティブフォルダを隠す ステップ' remote_folders: 'Read contents of the drive root folder:' rename_project_folder: '管理プロジェクトフォルダの名前を変更します:' sharepoint_sync_service: @@ -121,16 +121,16 @@ ja: rename_project_folder: '管理プロジェクトフォルダの名前を変更します:' errors: messages: - error: 予期しないエラーが発生しました。OpenProject のログを確認するか、管理者に連絡してください + error: 予期しないエラーが発生しました。OpenProject のログを確認するか、管理者に連絡してください。 forbidden: OpenProject could not access the requested resource. Please check your permissions configuration on the Storage Provider. unauthorized: OpenProjectはストレージプロバイダと認証できませんでした。アクセスできることを確認してください。 models: copy_project_folders_service: conflict: フォルダ %{destination_path} は既に存在する。上書きを避けるために処理を中断しています。 - error: 予期しないエラーが発生しました。OpenProject のログを確認するか、管理者に連絡してください - forbidden: OpenProject はソースフォルダにアクセスできませんでした。ストレージ・プロバイダの権限設定を確認してください + error: 予期しないエラーが発生しました。OpenProject のログを確認するか、管理者に連絡してください。 + forbidden: OpenProject はソースフォルダにアクセスできませんでした。ストレージ・プロバイダの権限設定を確認してください。 not_found: ソース・テンプレートの場所 %{source_path} が見つかりませんでした。 - unauthorized: OpenProject はストレージプロバイダと認証できませんでした。ストレージの設定を確認してください + unauthorized: OpenProject はストレージプロバイダと認証できませんでした。ストレージの設定を確認してください。 nextcloud_sync_service: attributes: add_user_to_group: @@ -151,31 +151,31 @@ ja: conflict: '以下の理由により、 %{user} のユーザーを %{group} グループから削除できませんでした: %{reason}' failed_to_remove: '以下の理由により、 %{user} のユーザーを %{group} グループから削除できませんでした: %{reason}' rename_project_folder: - conflict: OpenProjectは、同じ名前のフォルダが既に存在するため、プロジェクトフォルダの名前を %{current_path} に変更できませんでした - forbidden: OpenProject ユーザーは %{current_path} フォルダにアクセスできません。 - not_found: "%{current_path} は見つからなかった。" + conflict: OpenProjectは、同じ名前のフォルダが既に存在するため、プロジェクトフォルダの名前を %{current_path} に変更できませんでした。 + forbidden: OpenProjectユーザーは %{current_path} フォルダにアクセスできません。 + not_found: "%{current_path} は見つかりませんでした。" set_folders_permissions: - permission_not_set: '%{path}にパーミッションを設定できなかった。' - error: 予期しないエラーが発生しました。Nextcloud インスタンスに到達可能であることを確認し、OpenProject ワーカーのログを確認してください + permission_not_set: '%{path} に権限を設定できませんでした。' + error: 予期しないエラーが発生しました。Nextcloudインスタンスがアクセス可能であることを確認し、詳細についてはOpenProjectワーカーログを確認してください。 group_does_not_exist: "%{group} は存在しません。Nextcloudインスタンスの設定を確認してください。" - insufficient_privileges: OpenProjectには、 %{group}に %{user} を追加するのに十分な権限がありません。Nextcloudのグループ設定を確認してください。 - not_allowed: ネクストクラウドはリクエストをブロックする。 + insufficient_privileges: OpenProjectには %{user} を %{group}に追加するための十分な権限がありません。Nextcloudでグループ設定を確認してください。 + not_allowed: Nextcloudはリクエストをブロックします。 not_found: OpenProject could not find the file on the Nextcloud Storage Provider. Please check if it wasn't deleted. unauthorized: OpenProjectがNextcloudと同期できませんでした。ストレージとNextcloudの設定を確認してください。 - user_does_not_exist: "%{user} はNextcloudには存在しません。" + user_does_not_exist: "Nextcloudには%{user} は存在しません。" one_drive_sync_service: attributes: create_folder: - conflict: '%{folder_name} はすでに %{parent_location}に存在している。' - not_found: "%{parent_location} は見つからなかった。" + conflict: '%{folder_name} は %{parent_location} に既に存在します。' + not_found: "%{parent_location} は見つかりませんでした。" hide_inactive_folders: - permission_not_set: '%{path}にパーミッションを設定できなかった。' + permission_not_set: '%{path} に権限を設定できませんでした。' remote_folders: - request_error: OpenProject は %{drive_id}ドライブにアクセスできませんでした。ストレージの設定が正しいかどうか確認してください。 + request_error: OpenProjectがドライブ %{drive_id}にアクセスできませんでした。ストレージの設定が正しいか確認してください。 rename_project_folder: conflict: OpenProject could not rename the folder %{current_path} to %{project_folder_name} as a folder with the same name already exists. - forbidden: OpenProject は、 %{current_path} にアクセスできず、名前を変更できません。 - not_found: "%{current_path} は見つからなかった。" + forbidden: OpenProject は名前を変更するために %{current_path} にアクセスできません。 + not_found: "%{current_path} は見つかりませんでした。" set_folders_permissions: permission_not_set: '%{path} に権限を設定できませんでした。' error: An unexpected error occurred. Please ensure that OneDrive is reachable and check OpenProject worker logs for more information. @@ -303,41 +303,41 @@ ja: drive_id_format: ドライブIDフォーマット header: 構成 host: ホスト URL - host_url_accessible: アクセス可能なホストURL + host_url_accessible: ホスト URL アクセス storage_configured: 設定完了 - tenant_id: テナントID + tenant_id: Tenant ID failures: - other: "%{count} チェック失敗" + other: "%{count} チェックに失敗しました" success: すべてのチェックに合格 warnings: other: "%{count} は警告を返しました" connection_validation: client_id_invalid: 設定されたOAuth 2クライアントIDが無効です。設定を確認してください。 client_secret_invalid: 設定されたOAuth 2クライアントシークレットが無効です。設定を確認してください。 - nc_dependency_missing: 'ファイルストレージに必要な依存関係がありません。次の依存関係を追加してください: %{dependency}。' + nc_dependency_missing: 'ファイルストレージに必要な依存関係がありません。次の依存関係を追加してください: %{dependency}。' nc_dependency_version_mismatch: '%{dependency} アプリのバージョンがサポートされていません。Nextcloudサーバーをアップデートしてください。' nc_host_not_found: 設定されたホストURLにNextcloudサーバーが見つかりません。設定を確認してください。 nc_oauth_request_not_found: 現在接続しているユーザーを取得するエンドポイントが見つかりませんでした。詳細については、サーバーのログを確認してください。 nc_oauth_request_unauthorized: 現在のユーザーにはリモートファイルストレージにアクセスする権限がありません。サーバーのログを確認してください。 - nc_oauth_token_missing: OpenProjectでは、ユーザーがNextcloudアカウントをリンクしていないため、ユーザーレベルのNextcloudとの通信をテストできません。 + nc_oauth_token_missing: OpenProject は、Nextcloudアカウントへのリンクがまだないため、Nextcloudとのユーザーレベルの通信をテストできません。 nc_team_folder_not_found: The team folder could not be found. nc_unexpected_content: Unexpected content found in the managed team folder. nc_userless_access_denied: 設定されているアプリのパスワードが無効です。 not_configured: 接続を検証できませんでした。先に設定を完了してください。 - od_client_cant_delete_folder: クライアントがフォルダの削除に失敗しています。お使いのストレージのセットアップドキュメントを確認してください。 - od_client_write_permission_missing: クライアントの書き込み権限が不足しているようです。お使いのストレージのセットアップドキュメントを確認してください。 - od_drive_id_invalid: 設定されたドライブIDが無効のようです。設定を確認してください。 - od_drive_id_not_found: 設定されたドライブIDが見つかりません。設定を確認してください。 - od_oauth_request_not_found: 現在接続しているユーザーを取得するエンドポイントが見つかりませんでした。詳細については、サーバーのログを確認してください。 - od_oauth_request_unauthorized: 現在のユーザーにはリモートファイルストレージにアクセスする権限がありません。サーバーのログを確認してください。 - od_oauth_token_missing: OpenProjectは、ユーザーがまだMicrosoftアカウントをリンクしていないため、OneDriveとのユーザーレベルの通信をテストできません。 - od_tenant_id_wrong: 設定されたディレクトリ(テナント)IDが無効です。設定を確認してください。 + od_client_cant_delete_folder: クライアントがフォルダを削除できません。ストレージのセットアップドキュメントを確認してください。 + od_client_write_permission_missing: クライアントは書き込み権限がありません。ストレージの設定ドキュメントを確認してください。 + od_drive_id_invalid: 設定されたドライブ ID が無効です。設定を確認してください。 + od_drive_id_not_found: 設定されたドライブ ID が見つかりません。設定を確認してください。 + od_oauth_request_not_found: 現在接続されているユーザーを取得するエンドポイントが見つかりませんでした。詳細についてはサーバーログを確認してください。 + od_oauth_request_unauthorized: 現在のユーザーはリモートファイルストレージにアクセスする権限がありません。詳細についてはサーバーログを確認してください。 + od_oauth_token_missing: OpenProject は、ユーザーが Microsoft アカウントをまだリンクしていないため、OneDrive とのユーザー レベルの通信をテストできません。 + od_tenant_id_wrong: 設定されたディレクトリ (テナント) IDは無効です。設定を確認してください。 od_test_folder_exists: テストに必要なフォルダ %{folder_name} はすでに存在します。削除して再度お試しください。 od_unexpected_content: ドライブに予期しないコンテンツが見つかりました。 - offline_access_scope_missing: OpenID Connectプロバイダがoffline_accessスコープを要求するように設定することをお勧めします。統合はまだ機能するかもしれませんが、リフレッシュトークンの有効期限が切れていないことを確認してください。 + offline_access_scope_missing: offline_access スコープを要求するために OpenID Connect プロバイダを設定することをお勧めします。 統合はまだ動作するかもしれませんが、更新トークンが期限切れでないことを確認してください。 oidc_cant_refresh_token: ストレージへのアクセスを確認中にエラーが発生しました。詳細についてはサーバーログを確認してください。 - oidc_non_oidc_user: 現在のユーザーはプロビジョニングされていますが、OpenID Connect (OIDC) Identity Providerによってプロビジョニングされていません。OIDCプロビジョニングされたユーザーでチェックを再実行してください。 - oidc_non_provisioned_user: 現在のユーザはOpenID Connect Identity Providerから提供されていません。提供されたユーザーでチェックを再実行してください。 + oidc_non_oidc_user: 現在のユーザは、プロビジョニング中にOpenID Connect(OIDC)アイデンティティプロバイダによってプロビジョニングされていませんでした。OIDCプロビジョニングされたユーザでチェックを再実行してください。 + oidc_non_provisioned_user: 現在のユーザーはOpenID Connectアイデンティティプロバイダーによって提供されていません。指定されたユーザーとチェックを再実行してください。 oidc_provider_cant_exchange: OpenID Connectプロバイダはトークン交換をサポートしていないようですが、トークン交換はストレージ用に設定されています。 oidc_token_acquisition_failed: OpenID Connectのセットアップでは、必要なオーディエンスが提供されておらず、トークン交換機能も提供されていません。詳しくはドキュメントをご覧ください。 oidc_token_exchange_failed: OpenID Connect ProviderのToken Exchange設定に問題があるようです。設定を確認し、再度お試しください。 @@ -352,7 +352,7 @@ ja: sp_oauth_token_missing: OpenProject は、ユーザーがまだ SharePoint アカウントをリンクしていないため、ユーザーレベルの SharePoint との通信をテストできません。 sp_tenant_id_missing: 構成されたディレクトリ(テナント)IDがSharePointにありません。設定を確認してください。 sp_unexpected_content: Unexpected content found in the SharePoint Document Library. - unknown_error: 接続を検証できませんでした。不明なエラーが発生しました。詳細については、サーバーのログを確認してください。 + unknown_error: 接続を検証できませんでした。不明なエラーが発生しました。詳細についてはサーバーログを確認してください。 label_error: エラー label_failed: 失敗しました label_healthy: 健康的 @@ -360,55 +360,55 @@ ja: label_pending: 保留中 label_skipped: スキップ label_warning: 注意 - no_report: 報告書なし - no_report_description: 今すぐチェックを実行し、このファイル・ストレージの完全な健全性ステータスをレポートする。 + no_report: 利用可能なレポートがありません + no_report_description: 今すぐこのファイルストレージの完全な健康状態レポートを確認します。 open_report: 完全な健康報告を開く project_folders: subtitle: 自動的に管理されるプロジェクトフォルダ - since: '%{datetime}より' + since: '%{datetime} 以降' summary: - failure: いくつかのチェックに失敗し、システムが期待通りに機能しない。 - success: すべての接続とシステムは期待通りに機能している。 - warning: いくつかのチェックは警告を返した。これは予期せぬ動作につながる可能性がある。 - title: 健康状態報告 + failure: いくつかのチェックに失敗し、システムが期待どおりに動作しません。 + success: すべての接続とシステムは期待どおりに動作しています。 + warning: いくつかのチェックが警告を返しました。これは予期しない動作につながる可能性があります。 + title: 健康状態レポート health_email_notifications: description_disabled: 管理者は、重要なアップデートがあった場合、メールでアップデートを受け取ることはできません。 description_enabled: 管理者は、重要なアップデートがあった場合、メールで最新情報を受け取ります。 - error_could_not_be_saved: 電子メール通知の設定を保存できませんでした。もう一度お試しください。 + error_could_not_be_saved: メール通知設定を保存できませんでした。もう一度やり直してください。 title: 管理者にメールで更新する help_texts: - project_folder: プロジェクトフォルダは、このプロジェクトのファイルアップロード用のデフォルトフォルダです。それでも、ユーザーは他の場所にファイルをアップロードすることができます。 - project_folder_bulk: プロジェクトフォルダは、選択したすべてのプロジェクトのファイルアップロード用のデフォルトフォルダです。これは、各プロジェクト設定で個別に変更できます。それでも、ユーザーは他の場所にファイルをアップロードすることができます。 + project_folder: プロジェクトフォルダは、このプロジェクトのファイルアップロードのデフォルトフォルダです。ただし、ユーザーは他の場所にファイルをアップロードすることができます。 + project_folder_bulk: プロジェクトフォルダは、選択したすべてのプロジェクトのファイルアップロードのデフォルトフォルダです。 プロジェクトごとの設定で個別に変更することができますが、ユーザーは別の場所にファイルをアップロードすることもできます。 instructions: - all_available_storages_already_added: 利用可能なすべてのストレージはすでにプロジェクトに追加されている。 - authentication_method: OpenProject とストレージ間のリクエストの認証方法。 - automatic_folder: これにより、このプロジェクトのルート・フォルダーが自動的に作成され、各プロジェクト・メンバーのアクセス権が管理されます。 - empty_project_folder_validation: 続行するには、フォルダの選択が必須です。 - existing_manual_folder: 既存のフォルダをこのプロジェクトのルートフォルダとして指定することができます。ただし、パーミッションは自動的に管理されないため、管理者は関連するユーザーがアクセスできることを手動で確認する必要があります。選択したフォルダは、複数のプロジェクトで使用できます。 - host: https:// を含むストレージのホスト・アドレスを追加してください。255文字以内にしてください。 - managed_project_folders_application_password_caption: '%{provider_type_link}からこの値をコピーして、自動管理フォルダを有効にする。' - name: ユーザーが複数のストレージを区別できるように、ストレージに名前を付ける。 + all_available_storages_already_added: 利用可能なすべてのストレージが既にプロジェクトに追加されています。 + authentication_method: OpenProjectとストレージ間のリクエストは認証されます。 + automatic_folder: これにより、このプロジェクトのルートフォルダが自動的に作成され、各プロジェクトメンバーのアクセス権限が管理されます。 + empty_project_folder_validation: フォルダの選択は必須です。 + existing_manual_folder: このプロジェクトのルートフォルダとして既存のフォルダを指定できます。 ただし、権限は自動的に管理されておらず、管理者は関連するユーザーに手動でアクセス権があることを確認する必要があります。 選択したフォルダは複数のプロジェクトで使用できます。 + host: https://を含むストレージのホストアドレスを追加してください。255文字以内にしてください。 + managed_project_folders_application_password_caption: '%{provider_type_link} からこの値をコピーすることで、自動管理フォルダを有効にします。' + name: ユーザーが複数のストレージを区別できるように、ストレージに名前を付けます。 new_storage: 詳しくは、 %{provider_name} ファイルストレージ統合の設定に関するドキュメントをお読みください。 nextcloud: application_link_text: アプリケーション "Integration OpenProject" - integration: ネクストクラウド管理 / OpenProject + integration: Nextcloudの管理 / OpenProject oauth_configuration: '%{application_link_text} からこれらの値をコピーします。' - provider_configuration: セットアップを行う前に、Nextcloudインスタンスの管理者権限があり、 %{application_link_text} がインストールされていることを確認してください。 - storage_audience: Nextcloud インスタンスが ID プロバイダとの通信に使用するクライアント ID。 - storage_audience_placeholder: 例:ネクストクラウド - token_exchange_scope: トークン交換時に要求するスコープを、それぞれスペースで区切って指定する。 - no_specific_folder: デフォルトでは、ファイルをアップロードすると、各ユーザーは自分のホームフォルダから開始します。 - no_storage_set_up: ファイルストレージはまだ設定されていない。 - not_logged_into_storage: プロジェクトフォルダを選択するには、まずログインしてください。 + provider_configuration: Nextcloudインスタンスに管理権限があり、設定を行う前に %{application_link_text} がインストールされていることを確認してください。 + storage_audience: NextcloudインスタンスがIDプロバイダーと通信するために使用するクライアントID。 + storage_audience_placeholder: 例:nextcloud + token_exchange_scope: トークン交換中に要求されるべきスコープは、それぞれスペースで区切られています。 + no_specific_folder: デフォルトでは、各ユーザーはファイルをアップロードしたときに自分のホームフォルダから開始します。 + no_storage_set_up: まだ設定されているファイルストレージがありません。 + not_logged_into_storage: プロジェクトフォルダを選択するには、最初にログインしてください oauth_application_details: クライアントシークレットの値は、このウィンドウを閉じた後は二度とアクセスできなくなります。これらの値を %{oauth_application_details_link}にコピーしてください。 - oauth_application_details_link_text: NextcloudのOpenProject統合設定 + oauth_application_details_link_text: Nextcloud OpenProjectインテグレーション設定 one_drive: application_link_text: Azure Portal copy_redirect_uri: リダイレクトURIをコピーする documentation_link_text: OneDriveファイルストレージのドキュメント drive_id: '%{drive_id_link_text} の手順に従って、目的のドライブからIDをコピーしてください。' - integration: ワンドライブ - missing_client_id_for_redirect_uri: OAuthの値を入力してURIを生成してください。 + integration: OneDrive + missing_client_id_for_redirect_uri: OAuthの値を入力してURIを生成してください oauth_client_redirect_uri: この値を「リダイレクト URIs」にある新しい Web リダイレクト URI にコピーしてください。 oauth_client_secret: Client 資格情報にアプリケーション クライアント シークレットがない場合は、新しいシークレットを作成してください。 oauth_configuration: '%{application_link_text}、目的のアプリケーションからこれらの値をコピーします。' @@ -480,13 +480,13 @@ ja: login_button_aria_label: '%{storage} にログイン' login_button_label: "%{provider_type} ログイン" project_settings: - description: プロジェクトフォルダにアクセスするには、 %{storage}にログインする必要があります。 + description: プロジェクトフォルダにアクセスするには、 %{storage} にログインする必要があります。 requesting_access_to: '%{storage} へのアクセスをリクエストしています' storage_admin: description: このストレージにプロジェクトを追加するには、 %{provider_type}にログインする必要があります。ログインしてもう一度やり直してください。 open_project_storage_modal: success: - subtitle: リダイレクトされます + subtitle: リダイレクトしています title: 連携のセットアップが完了しました timeout: link_text: ファイルストレージセットアップの状態の状態 @@ -505,8 +505,8 @@ ja: subtitle_short: OpenProjectにプロジェクトごとにフォルダを自動的に作成させます。 title: 自動的に管理されるプロジェクトフォルダ project_settings: - edit: このプロジェクトのファイル・ストレージを編集する - members_connection_status: メンバーの接続状況 + edit: このプロジェクトのファイルストレージを編集 + members_connection_status: 会員の接続状況 new: このプロジェクトにファイルストレージを追加する project_storage_members: subtitle: 全プロジェクトメンバーのストレージ %{storage_name_link} の接続状態を確認する。 @@ -517,14 +517,14 @@ ja: provider_types: label: プロバイダー・タイプ nextcloud: - label_oauth_client_id: NextcloudのOAuthクライアントID - label_oauth_client_secret: NextcloudOAuthクライアントシークレット + label_oauth_client_id: Nextcloud OAuthクライアントID + label_oauth_client_secret: Nextcloud OAuth クライアントシークレット name: ネクストクラウド name_placeholder: 例:ネクストクラウド one_drive: - label_oauth_client_id: Azure OAuthアプリケーション(クライアント)ID + label_oauth_client_id: Azure OAuth アプリケーション (クライアント) ID label_oauth_client_secret: Azure OAuth クライアントの秘密値 - name: ワンドライブ + name: OneDrive name_placeholder: '例: OneDrive' sharepoint: drive_description: OpenProject access-managed document library @@ -534,18 +534,18 @@ ja: name_placeholder: 例:シェアポイント show_attachments_toggle: description: このオプションを無効にすると、作業パッケージのファイルタブの添付ファイルリストが非表示になります。ワークパッケージの説明に添付されたファイルは、内部添付ファイルストレージにアップロードされます。 - label: ワークパッケージのファイルタブに添付ファイルを表示 + label: ワークパッケージファイルタブに添付ファイルを表示 storage_audience: - documentation_intro: 以下のオプションと ID プロバイダの設定の詳細については、当社のドキュメントをお読みください。 + documentation_intro: アイデンティティプロバイダの以下のオプションと設定については、当社のドキュメントをお読みください。 idp: - helptext: OpenProjectは、ストレージへのリクエストを認証するために、ログイン時にIDプロバイダから受け取ったアクセストークンを使用します。別のトークンを取得しようとすることはありません。 - label: ユーザーログイン時に取得したアクセストークンを使用する + helptext: OpenProjectはログイン中にIDプロバイダーが受け取ったアクセストークンを使用して、ストレージへのリクエストを認証します。 別のトークンを取得しようとしません。 + label: ログイン中に取得したアクセストークンを使用する manual: - helptext: OpenProjectは、指定されたオーディエンスのIDプロバイダとトークンを交換します。 + helptext: OpenProject は、特定のオーディエンスの ID プロバイダーとトークンを交換します。 label: Manually specify audience for which to exchange access token (Recommended) storage_list_blank_slate: - description: ストレージを追加して、ここで見ることができる。 - heading: あなたはまだ倉庫を持っていない。 + description: ここにそれらを見るためにストレージを追加します。 + heading: まだストレージがありません。 successful_storage_connection: ストレージが正常に接続されました! 使用する各プロジェクトの「プロジェクト」タブでストレージをアクティブにすることを忘れないでください。 upsell: one_drive: diff --git a/modules/storages/config/locales/crowdin/js-ja.yml b/modules/storages/config/locales/crowdin/js-ja.yml index 04cb36084f5..35563523140 100644 --- a/modules/storages/config/locales/crowdin/js-ja.yml +++ b/modules/storages/config/locales/crowdin/js-ja.yml @@ -3,14 +3,14 @@ ja: js: storages: authentication_error: "%{storageType} での認証に失敗しました" - link_files_in_storage: "リンクファイル %{storageType}" - link_existing_files: "既存のファイルをリンク" - upload_files: "ファイルのアップロード" + link_files_in_storage: "%{storageType}のファイルをリンクする" + link_existing_files: "既存のファイルをリンクする" + upload_files: "ログファイル" drop_files: "ここにファイルをドロップして、 %{name} にアップロードします。" drop_or_click_files: "ここにファイルをドロップするか、クリックして %{name} にアップロードします。" login: "%{storageType} ログイン" login_to: "%{storageType}にログイン" - no_connection: "%{storageType} 接続がありません" + no_connection: "%{storageType} 接続なし" open_storage: "%{storageType} を開く" select_location: "場所を選択" choose_location: "場所を選ぶ" @@ -24,7 +24,7 @@ ja: authentication_error: "%{storageType} へのリクエストを認証できませんでした。これはエラーです。" connection_error: > %{storageType} の設定が一部機能していません。 %{storageType} 管理者にお問い合わせください。 - live_data_error: "ファイルの詳細の取得に失敗しました" + live_data_error: "ファイル詳細の取得エラー" live_data_error_description: > 一部の %{storageType} データを取得できませんでした。このページを再読み込みするか、 %{storageType} 管理者にお問い合わせください。 no_file_links: "このワークパッケージにファイルをリンクするには、 %{storageType}を使用してください。" @@ -33,7 +33,7 @@ ja: suggest_logout: ログアウトしてログインし直すと、この問題が解決するかどうか試してみてください。 suggest_relink: 以下のログインボタンからアカウントを再リンクすると、この問題が解決するかどうか試してみてください。 files: - already_existing_header: "このファイルはすでに存在する" + already_existing_header: "このファイルは既に存在します" already_existing_body: > このファイルをアップロードしようとしている場所に、"%{fileName}"という名前のファイルがすでに存在します。どうしますか? directory_not_writeable: "このフォルダにファイルを追加する権限がありません。" @@ -41,7 +41,7 @@ ja: dragging_folder: "%{storageType} へのアップロードはフォルダをサポートしていません。" empty_folder: "このフォルダは空です。" empty_folder_location_hint: "下のボタンをクリックして、この場所にファイルをアップロードしてください。" - file_not_selectable_location: "場所を選択する過程でファイルを選択することはできない。" + file_not_selectable_location: "ファイルを選択することは、場所を選択する過程ではできません。" project_folder_no_access: > プロジェクトフォルダにアクセスできません。管理者に連絡してアクセス権を取得するか、別の場所にファイルをアップロードしてください。 managed_project_folder_not_available: > @@ -79,9 +79,9 @@ ja: ファイル (%{fileName}) の容量がストレージ・クォータの許容量を超えています。管理者に連絡して、このクォータを変更してください。 detail: nextcloud: > - 最新版のNextcloudアプリ「OpenProject Integration」がインストールされていることを確認し、管理者にお問い合わせください。 + Nextcloudアプリ「OpenProject統合」の最新バージョンがインストールされていることを確認し、詳細については管理者にお問い合わせください。 link_uploaded_file_error: > - 最近アップロードされたファイル '%{fileName}' をワークパッケージ %{workPackageId}にリンクするエラーが発生しました。 + 最近アップロードされたファイル '%{fileName}' をワークパッケージ %{workPackageId} にリンクしてエラーが発生しました。 tooltip: not_logged_in: "このファイルにアクセスするには、ストレージにログインしてください。" view_not_allowed: "このファイルを閲覧する権限がありません。" diff --git a/modules/team_planner/config/locales/crowdin/js-fr.yml b/modules/team_planner/config/locales/crowdin/js-fr.yml index 4d8a0c78286..c96442a127a 100644 --- a/modules/team_planner/config/locales/crowdin/js-fr.yml +++ b/modules/team_planner/config/locales/crowdin/js-fr.yml @@ -19,7 +19,7 @@ fr: today: 'Aujourd''hui' drag_here_to_remove: 'Faites glisser ici pour supprimer le responsable et les dates de début et de fin.' cannot_drag_here: 'Impossible de déplacer le lot de travail en raison de restrictions d''autorisation ou d''édition.' - cannot_drag_to_non_working_day: 'Ce lot de travaux ne peut pas démarrer/terminer sur un jour non ouvré.' + cannot_drag_to_non_working_day: 'Ce lot de travail ne peut pas démarrer/terminer sur un jour non ouvré.' quick_add: empty_state: 'Utilisez le champ de recherche pour trouver des lots de travaux et faites-les glisser vers le planificateur pour l''assigner à quelqu''un et définir des dates de début et de fin.' search_placeholder: 'Rechercher...' diff --git a/modules/two_factor_authentication/config/locales/crowdin/ro.yml b/modules/two_factor_authentication/config/locales/crowdin/ro.yml index 6c8ef8748d3..7feb54c4620 100644 --- a/modules/two_factor_authentication/config/locales/crowdin/ro.yml +++ b/modules/two_factor_authentication/config/locales/crowdin/ro.yml @@ -178,7 +178,7 @@ ro: label_expiration_hint: "%{date} sau la deconectare" label_actions: "Acțiuni" label_confirmed: "Confirmat" - button_continue: "Continuă" + button_continue: "Continuaţi" button_make_default: "Marcați ca implicit" label_unverified_phone: "Telefonul mobil nu a fost încă verificat" notice_phone_number_format: "Te rog să introduci numărul în următorul format: +XX XXXXXXXX." diff --git a/modules/two_factor_authentication/config/locales/crowdin/ru.yml b/modules/two_factor_authentication/config/locales/crowdin/ru.yml index 80fa7dc0e25..bdba7c151d8 100644 --- a/modules/two_factor_authentication/config/locales/crowdin/ru.yml +++ b/modules/two_factor_authentication/config/locales/crowdin/ru.yml @@ -178,7 +178,7 @@ ru: label_expiration_hint: "%{date} или при выходе из системы" label_actions: "Действия" label_confirmed: "Подтвержден" - button_continue: "Продолжить" + button_continue: "Далее" button_make_default: "Задать по умолчанию" label_unverified_phone: "Сотовый телефон еще не подтвержден" notice_phone_number_format: "Введите номер в следующем формате: +XX XXXXXXXX." diff --git a/modules/two_factor_authentication/config/locales/crowdin/uk.yml b/modules/two_factor_authentication/config/locales/crowdin/uk.yml index 944c3bc181d..8649db79efe 100644 --- a/modules/two_factor_authentication/config/locales/crowdin/uk.yml +++ b/modules/two_factor_authentication/config/locales/crowdin/uk.yml @@ -119,7 +119,7 @@ uk: failed_to_delete: "Не вдалося видалити пристрій 2FA." is_default_cannot_delete: "Пристрій позначено як типовий і його не можна видалити через активну політику безпеки. Перед видаленням позначте інший пристрій як стандартний." not_existing: "Для вашого облікового запису не зареєстровано жодного пристрою 2FA." - 2fa_from_input: Введіть код, отриманий на пристрій %{device_name}, щоб підтвердити свою особу. + 2fa_from_input: Введіть код, що надійшов на пристрій %{device_name}, щоб підтвердити свою особу. 2fa_from_webauthn: Укажіть пристрій WebAuthn %{device_name}. Якщо це USB-пристрій, переконайтеся, що його підключено, і торкніться його. Потім натисніть кнопку входу. webauthn: title: "WebAuthn" diff --git a/modules/xls_export/config/locales/crowdin/zh-CN.yml b/modules/xls_export/config/locales/crowdin/zh-CN.yml index 59230e603ec..31c8d3cdd71 100644 --- a/modules/xls_export/config/locales/crowdin/zh-CN.yml +++ b/modules/xls_export/config/locales/crowdin/zh-CN.yml @@ -13,4 +13,4 @@ zh-CN: xls_with_relations: "带关系的 XLS" xls_export: child_of: 此项的子项 - parent_of: 此项的父级 + parent_of: 此项的父项 From 1cb3b85d9aeaf08e453d624a0df37559efe2521d Mon Sep 17 00:00:00 2001 From: OpenProject Actions CI Date: Sun, 1 Feb 2026 03:59:20 +0000 Subject: [PATCH 039/293] update locales from crowdin [ci skip] --- config/locales/crowdin/cs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/crowdin/cs.yml b/config/locales/crowdin/cs.yml index 2cc63b327b4..cef3d45acf8 100644 --- a/config/locales/crowdin/cs.yml +++ b/config/locales/crowdin/cs.yml @@ -3031,7 +3031,7 @@ cs: ai: "Artificial Intelligence (AI)" aggregation: "Agregace" api_and_webhooks: "API & Webhooky" - mail_notification: "E-mailové notifikace" + mail_notification: "E-mailová upozornění" mails_and_notifications: "E-maily a oznámení" mcp_configurations: "Model Context Protocol (MCP)" quick_add: From 4d1dcccd00dc56168d9630aedcbbd4d998198f7e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Feb 2026 01:44:46 -0300 Subject: [PATCH 040/293] Bump the aws-gems group with 3 updates (#21724) Bumps the aws-gems group with 3 updates: [aws-sdk-core](https://github.com/aws/aws-sdk-ruby), [aws-sdk-s3](https://github.com/aws/aws-sdk-ruby) and [aws-sdk-sns](https://github.com/aws/aws-sdk-ruby). Updates `aws-sdk-core` from 3.241.3 to 3.241.4 - [Release notes](https://github.com/aws/aws-sdk-ruby/releases) - [Changelog](https://github.com/aws/aws-sdk-ruby/blob/version-3/gems/aws-sdk-core/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-ruby/commits) Updates `aws-sdk-s3` from 1.211.0 to 1.212.0 - [Release notes](https://github.com/aws/aws-sdk-ruby/releases) - [Changelog](https://github.com/aws/aws-sdk-ruby/blob/version-3/gems/aws-sdk-s3/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-ruby/commits) Updates `aws-sdk-sns` from 1.111.0 to 1.112.0 - [Release notes](https://github.com/aws/aws-sdk-ruby/releases) - [Changelog](https://github.com/aws/aws-sdk-ruby/blob/version-3/gems/aws-sdk-sns/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-ruby/commits) --- updated-dependencies: - dependency-name: aws-sdk-core dependency-version: 3.241.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: aws-gems - dependency-name: aws-sdk-s3 dependency-version: 1.212.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: aws-gems - dependency-name: aws-sdk-sns dependency-version: 1.112.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: aws-gems ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 30 +++++++++---------- ...nproject-two_factor_authentication.gemspec | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Gemfile b/Gemfile index a26fa31ee8e..cea6d80f8ca 100644 --- a/Gemfile +++ b/Gemfile @@ -200,7 +200,7 @@ gem "fog-aws" gem "aws-sdk-core", "~> 3.241" # File upload via fog + screenshots on travis -gem "aws-sdk-s3", "~> 1.211" +gem "aws-sdk-s3", "~> 1.213" gem "openproject-token", "~> 8.5.0" diff --git a/Gemfile.lock b/Gemfile.lock index 214bb5e56ea..829c7f04ded 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -203,7 +203,7 @@ PATH remote: modules/two_factor_authentication specs: openproject-two_factor_authentication (1.0.0) - aws-sdk-sns (>= 1.101, < 1.112) + aws-sdk-sns (>= 1.101, < 1.113) messagebird-rest (>= 1.4.2, < 5.1.0) rotp (~> 6.1) webauthn (~> 3.0) @@ -339,8 +339,8 @@ GEM awesome_nested_set (3.9.0) activerecord (>= 4.0.0, < 8.2) aws-eventstream (1.4.0) - aws-partitions (1.1202.0) - aws-sdk-core (3.241.3) + aws-partitions (1.1210.0) + aws-sdk-core (3.241.4) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) aws-sigv4 (~> 1.9) @@ -348,15 +348,15 @@ GEM bigdecimal jmespath (~> 1, >= 1.6.1) logger - aws-sdk-kms (1.120.0) - aws-sdk-core (~> 3, >= 3.241.3) + aws-sdk-kms (1.121.0) + aws-sdk-core (~> 3, >= 3.241.4) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.211.0) - aws-sdk-core (~> 3, >= 3.241.3) + aws-sdk-s3 (1.213.0) + aws-sdk-core (~> 3, >= 3.241.4) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) - aws-sdk-sns (1.111.0) - aws-sdk-core (~> 3, >= 3.241.3) + aws-sdk-sns (1.112.0) + aws-sdk-core (~> 3, >= 3.241.4) aws-sigv4 (~> 1.5) aws-sigv4 (1.12.1) aws-eventstream (~> 1, >= 1.0.2) @@ -1548,7 +1548,7 @@ DEPENDENCIES auto_strip_attributes (~> 2.5) awesome_nested_set (~> 3.9.0) aws-sdk-core (~> 3.241) - aws-sdk-s3 (~> 1.211) + aws-sdk-s3 (~> 1.213) axe-core-rspec bcrypt (~> 3.1.6) bootsnap (~> 1.20.0) @@ -1788,11 +1788,11 @@ CHECKSUMS auto_strip_attributes (2.6.0) sha256=a7e2e0cf744de2bcd947fd68014220702bcc88c81274c1cd9ce6f7316aae39b0 awesome_nested_set (3.9.0) sha256=3ce99e816550f97f4de118e621630070aacf24928b920fe4a68846578a8daaed aws-eventstream (1.4.0) sha256=116bf85c436200d1060811e6f5d2d40c88f65448f2125bc77ffce5121e6e183b - aws-partitions (1.1202.0) sha256=c8aa0f134a23464c61cfd00edfb4b6d968b01847a8b591d4dcc0c63a4897c301 - aws-sdk-core (3.241.3) sha256=c7c445ecf1c601c860fd537458b2eb8df0c5df01e63c371849e6594e6b1d4f47 - aws-sdk-kms (1.120.0) sha256=a206ac6f62efbe971f802e8399d2702496a5c5bc800abcf94ead87bdddfdfd80 - aws-sdk-s3 (1.211.0) sha256=2ae5feb09ff4862462824f267b76601ed16922a15de56cf51e4fa99bc5b3f519 - aws-sdk-sns (1.111.0) sha256=195edbd6953d4caa2748bd4861e0fe8df54d90aadf07a0ac268987b946c7e5be + aws-partitions (1.1210.0) sha256=04143b868f8b3fc481f68552df6a430f1083a56e2afec5a6bc5c89532ab423fe + aws-sdk-core (3.241.4) sha256=a42ccba8c24ea9800e7b6c40aa201c967458f7c460044a6eebf64fbf1226e4fd + aws-sdk-kms (1.121.0) sha256=d563c1cfb4b5754efbc671216c8eca875338748adad0f42518c28dfa0a2d01e0 + aws-sdk-s3 (1.213.0) sha256=af596ccf544582406db610e95cc9099276eaf03142f57a2f30f76940e598e50d + aws-sdk-sns (1.112.0) sha256=aff1b1b5bbcb4229599221c558a41790c1cd1a1fed47ac3d27d27512ad24b254 aws-sigv4 (1.12.1) sha256=6973ff95cb0fd0dc58ba26e90e9510a2219525d07620c8babeb70ef831826c00 axe-core-api (4.11.0) sha256=3d9c94e3c8f8f9b8f154a3ce036b3dec2dabf7bb7de5e51d663b18bd8a0d691b axe-core-rspec (4.11.0) sha256=3c3e3ef3863d9f5243e056b7da328932c0b6682dda299bb4bd74d760641486d7 diff --git a/modules/two_factor_authentication/openproject-two_factor_authentication.gemspec b/modules/two_factor_authentication/openproject-two_factor_authentication.gemspec index 464c30f861e..6779ba5d558 100644 --- a/modules/two_factor_authentication/openproject-two_factor_authentication.gemspec +++ b/modules/two_factor_authentication/openproject-two_factor_authentication.gemspec @@ -16,6 +16,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.101", "< 1.112" + s.add_dependency "aws-sdk-sns", ">= 1.101", "< 1.113" s.metadata["rubygems_mfa_required"] = "true" end From 468914eba916c9ac9bcba2a6c2487223809b961f Mon Sep 17 00:00:00 2001 From: OpenProject Actions CI Date: Mon, 2 Feb 2026 03:53:30 +0000 Subject: [PATCH 041/293] update locales from crowdin [ci skip] --- config/locales/crowdin/zh-TW.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/crowdin/zh-TW.yml b/config/locales/crowdin/zh-TW.yml index 9c65bda8c91..b456e37fd92 100644 --- a/config/locales/crowdin/zh-TW.yml +++ b/config/locales/crowdin/zh-TW.yml @@ -1502,7 +1502,7 @@ zh-TW: even: "必須是偶數" exclusion: "已保留" feature_disabled: 不可用。 - feature_disabled_for_project: is disabled for this project. + feature_disabled_for_project: 對於此專案已停用。 file_too_large: "太大 (最大為 %{count} Bytes)." filter_does_not_exist: "過濾條件不存在" format: "與預期的格式“%{expected}”不符" From d0ef12744cbcb9f28a1b673703eacf9b81a38b11 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 05:51:08 +0000 Subject: [PATCH 042/293] Bump rails from 8.0.4 to 8.1.2 Bumps [rails](https://github.com/rails/rails) from 8.0.4 to 8.1.2. - [Release notes](https://github.com/rails/rails/releases) - [Commits](https://github.com/rails/rails/compare/v8.0.4...v8.1.2) --- updated-dependencies: - dependency-name: rails dependency-version: 8.1.2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Gemfile | 2 +- Gemfile.lock | 148 ++++++++++++++++++++++++++------------------------- 2 files changed, 77 insertions(+), 73 deletions(-) diff --git a/Gemfile b/Gemfile index cea6d80f8ca..f3e086054da 100644 --- a/Gemfile +++ b/Gemfile @@ -41,7 +41,7 @@ gem "activemodel-serializers-xml", "~> 1.0.1" gem "activerecord-import", "~> 2.2.0" gem "activerecord-session_store", "~> 2.2.0" gem "ox" -gem "rails", "~> 8.0.4" +gem "rails", "~> 8.1.2" gem "responders", "~> 3.2" gem "ffi", "~> 1.15" diff --git a/Gemfile.lock b/Gemfile.lock index 829c7f04ded..434aa3e694d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -223,29 +223,31 @@ GEM remote: https://rubygems.org/ specs: Ascii85 (2.0.1) - actioncable (8.0.4) - actionpack (= 8.0.4) - activesupport (= 8.0.4) + action_text-trix (2.1.16) + railties + actioncable (8.1.2) + actionpack (= 8.1.2) + activesupport (= 8.1.2) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (8.0.4) - actionpack (= 8.0.4) - activejob (= 8.0.4) - activerecord (= 8.0.4) - activestorage (= 8.0.4) - activesupport (= 8.0.4) + actionmailbox (8.1.2) + actionpack (= 8.1.2) + activejob (= 8.1.2) + activerecord (= 8.1.2) + activestorage (= 8.1.2) + activesupport (= 8.1.2) mail (>= 2.8.0) - actionmailer (8.0.4) - actionpack (= 8.0.4) - actionview (= 8.0.4) - activejob (= 8.0.4) - activesupport (= 8.0.4) + actionmailer (8.1.2) + actionpack (= 8.1.2) + actionview (= 8.1.2) + activejob (= 8.1.2) + activesupport (= 8.1.2) mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (8.0.4) - actionview (= 8.0.4) - activesupport (= 8.0.4) + actionpack (8.1.2) + actionview (= 8.1.2) + activesupport (= 8.1.2) nokogiri (>= 1.8.5) rack (>= 2.2.4) rack-session (>= 1.0.1) @@ -256,33 +258,34 @@ GEM actionpack-xml_parser (2.0.1) actionpack (>= 5.0) railties (>= 5.0) - actiontext (8.0.4) - actionpack (= 8.0.4) - activerecord (= 8.0.4) - activestorage (= 8.0.4) - activesupport (= 8.0.4) + actiontext (8.1.2) + action_text-trix (~> 2.1.15) + actionpack (= 8.1.2) + activerecord (= 8.1.2) + activestorage (= 8.1.2) + activesupport (= 8.1.2) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (8.0.4) - activesupport (= 8.0.4) + actionview (8.1.2) + activesupport (= 8.1.2) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) active_record_doctor (2.0.1) activerecord (>= 7.0.0) - activejob (8.0.4) - activesupport (= 8.0.4) + activejob (8.1.2) + activesupport (= 8.1.2) globalid (>= 0.3.6) - activemodel (8.0.4) - activesupport (= 8.0.4) + activemodel (8.1.2) + activesupport (= 8.1.2) activemodel-serializers-xml (1.0.3) activemodel (>= 5.0.0.a) activesupport (>= 5.0.0.a) builder (~> 3.1) - activerecord (8.0.4) - activemodel (= 8.0.4) - activesupport (= 8.0.4) + activerecord (8.1.2) + activemodel (= 8.1.2) + activesupport (= 8.1.2) timeout (>= 0.4.0) activerecord-import (2.2.0) activerecord (>= 4.2) @@ -294,20 +297,20 @@ GEM cgi (>= 0.3.6) rack (>= 2.0.8, < 4) railties (>= 7.0) - activestorage (8.0.4) - actionpack (= 8.0.4) - activejob (= 8.0.4) - activerecord (= 8.0.4) - activesupport (= 8.0.4) + activestorage (8.1.2) + actionpack (= 8.1.2) + activejob (= 8.1.2) + activerecord (= 8.1.2) + activesupport (= 8.1.2) marcel (~> 1.0) - activesupport (8.0.4) + activesupport (8.1.2) base64 - benchmark (>= 0.3) bigdecimal concurrent-ruby (~> 1.0, >= 1.3.1) connection_pool (>= 2.2.5) drb i18n (>= 1.6, < 2) + json logger (>= 1.4.2) minitest (>= 5.1) securerandom (>= 0.3) @@ -1106,7 +1109,7 @@ GEM prawn-table (0.2.2) prawn (>= 1.3.0, < 3.0.0) prettyprint (0.2.0) - prism (1.8.0) + prism (1.9.0) prometheus-client-mmap (1.5.0) base64 bigdecimal @@ -1199,20 +1202,20 @@ GEM rackup (1.0.1) rack (< 3) webrick - rails (8.0.4) - actioncable (= 8.0.4) - actionmailbox (= 8.0.4) - actionmailer (= 8.0.4) - actionpack (= 8.0.4) - actiontext (= 8.0.4) - actionview (= 8.0.4) - activejob (= 8.0.4) - activemodel (= 8.0.4) - activerecord (= 8.0.4) - activestorage (= 8.0.4) - activesupport (= 8.0.4) + rails (8.1.2) + actioncable (= 8.1.2) + actionmailbox (= 8.1.2) + actionmailer (= 8.1.2) + actionpack (= 8.1.2) + actiontext (= 8.1.2) + actionview (= 8.1.2) + activejob (= 8.1.2) + activemodel (= 8.1.2) + activerecord (= 8.1.2) + activestorage (= 8.1.2) + activesupport (= 8.1.2) bundler (>= 1.15.0) - railties (= 8.0.4) + railties (= 8.1.2) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1) @@ -1227,9 +1230,9 @@ GEM rails-i18n (8.1.0) i18n (>= 0.7, < 2) railties (>= 8.0.0, < 9) - railties (8.0.4) - actionpack (= 8.0.4) - activesupport (= 8.0.4) + railties (8.1.2) + actionpack (= 8.1.2) + activesupport (= 8.1.2) irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) @@ -1423,7 +1426,7 @@ GEM unicode-display_width (>= 1.1.1, < 4) test-prof (1.4.4) text-hyphen (1.5.0) - thor (1.4.0) + thor (1.5.0) thread_safe (0.3.6) timecop (0.9.10) timeout (0.6.0) @@ -1686,7 +1689,7 @@ DEPENDENCIES rack-test (~> 2.2.0) rack-timeout (~> 0.7.0) rack_session_access - rails (~> 8.0.4) + rails (~> 8.1.2) rails-controller-testing (~> 1.0.2) rails-i18n (~> 8.1.0) rbtrace @@ -1756,23 +1759,24 @@ DEPENDENCIES CHECKSUMS Ascii85 (2.0.1) sha256=15cb5d941808543cbb9e7e6aea3c8ec3877f154c3461e8b3673e97f7ecedbe5a - actioncable (8.0.4) sha256=aadb2bf2977b666cfeaa7dee66fd50e147559f78a8d55f6169e913502475e09f - actionmailbox (8.0.4) sha256=ed0b634a502fb63d1ba01ae025772e9d0261b7ba12e66389c736fcf4635cd80f - actionmailer (8.0.4) sha256=3b9270d8e19f0afb534b11c52f439937dc30028adcbbae2b244f3383ce75de4b - actionpack (8.0.4) sha256=0364c7582f32c8f404725fa30d3f6853f834c5f4964afd4a072b848c8a23cddb + action_text-trix (2.1.16) sha256=f645a2c21821b8449fd1d6770708f4031c91a2eedf9ef476e9be93c64e703a8a + actioncable (8.1.2) sha256=dc31efc34cca9cdefc5c691ddb8b4b214c0ea5cd1372108cbc1377767fb91969 + actionmailbox (8.1.2) sha256=058b2fb1980e5d5a894f675475fcfa45c62631103d5a2596d9610ec81581889b + actionmailer (8.1.2) sha256=f4c1d2060f653bfe908aa7fdc5a61c0e5279670de992146582f2e36f8b9175e9 + actionpack (8.1.2) sha256=ced74147a1f0daafaa4bab7f677513fd4d3add574c7839958f7b4f1de44f8423 actionpack-xml_parser (2.0.1) sha256=40cb461ee99445314ab580a783fb7413580deb8b28113c9e70ecd7c1b334d5e6 - actiontext (8.0.4) sha256=40b3970268ac29b865685456b2586df5052d068fd0cb04acb2291e737cea2340 - actionview (8.0.4) sha256=5bd3c41ee7a59e14cf062bb5e4ee53c9a253d12fc13c8754cae368012e1a1648 + actiontext (8.1.2) sha256=0bf57da22a9c19d970779c3ce24a56be31b51c7640f2763ec64aa72e358d2d2d + actionview (8.1.2) sha256=80455b2588911c9b72cec22d240edacb7c150e800ef2234821269b2b2c3e2e5b active_record_doctor (2.0.1) sha256=7af0ac02195385c8f2f67d0e4ebe72b1fc79d65eaaf329e0db07f4d12a84069a - activejob (8.0.4) sha256=cbc8a85d0e168cb90a5629c8a36fe2d08ba840103d3aed3eee0c7beb784fccce - activemodel (8.0.4) sha256=8f4e4fac3cd104b1bf30419c3745206f6f724c0e2902a939b4113f4c90730dfd + activejob (8.1.2) sha256=908dab3713b101859536375819f4156b07bdf4c232cc645e7538adb9e302f825 + activemodel (8.1.2) sha256=e21358c11ce68aed3f9838b7e464977bc007b4446c6e4059781e1d5c03bcf33e activemodel-serializers-xml (1.0.3) sha256=fa1b16305e7254cc58a59c68833e3c0a593a59c8ab95d3be5aaea7cd9416c397 - activerecord (8.0.4) sha256=bda32c171799e5ca5460447d3b7272ed14447244e2497abf2107f87fc44cbf32 + activerecord (8.1.2) sha256=acfbe0cadfcc50fa208011fe6f4eb01cae682ebae0ef57145ba45380c74bcc44 activerecord-import (2.2.0) sha256=f8ca99b196e50775723d1f1d192c379f656378dc9f5628240992a0d78807fa4b activerecord-nulldb-adapter (1.2.2) sha256=01e0b2e49af11ad56a92e274a3d8c9fb3c50a12a5460218c4c4b45355d9ef968 activerecord-session_store (2.2.0) sha256=65918054573683bf4f87af89e765e1fece14c9d71cfac1f11abe4687c96e2743 - activestorage (8.0.4) sha256=47f312962fc898c1669f20cf7448d19668a5547f4a5f64e59a837d9d3f64a043 - activesupport (8.0.4) sha256=894a3a6c7733b5fae5a7df3acd76c4b563f38687df8a04fa3cbd25360f3fe95a + activestorage (8.1.2) sha256=8a63a48c3999caeee26a59441f813f94681fc35cc41aba7ce1f836add04fba76 + activesupport (8.1.2) sha256=88842578ccd0d40f658289b0e8c842acfe9af751afee2e0744a7873f50b6fdae acts_as_list (1.2.6) sha256=8345380900b7bee620c07ad00991ccee59af3d8c9e8574f426e321da2865fdc8 acts_as_tree (2.9.1) sha256=b869eb10a8de38616b64ffcf9e882d3d99c8e06909c4057078a76c3b89a9a2f3 addressable (2.8.8) sha256=7c13b8f9536cf6364c03b9d417c19986019e28f7c00ac8132da4eb0fe393b057 @@ -2119,7 +2123,7 @@ CHECKSUMS prawn (2.4.0) sha256=82062744f7126c2d77501da253a154271790254dfa8c309b8e52e79bc5de2abd prawn-table (0.2.2) sha256=336d46e39e003f77bf973337a958af6a68300b941c85cb22288872dc2b36addb prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193 - prism (1.8.0) sha256=84453a16ef5530ea62c5f03ec16b52a459575ad4e7b9c2b360fd8ce2c39c1254 + prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85 prometheus-client-mmap (1.5.0) sha256=361eb98d6c19ae0f44ae5e02f9e6750436fd92d1c501d1c69843609c1daf0117 prometheus-client-mmap (1.5.0-aarch64-linux-gnu) sha256=e7fe1a630dda83a237efb0eb4a29ee3da37922722fc89ecac6057a1187372c5d prometheus-client-mmap (1.5.0-aarch64-linux-musl) sha256=897fa5d82150ddcb3bc30dfa7af0deb85930655500e71bd8879daa86b5e690ff @@ -2149,12 +2153,12 @@ CHECKSUMS rack-timeout (0.7.0) sha256=757337e9793cca999bb73a61fe2a7d4280aa9eefbaf787ce3b98d860749c87d9 rack_session_access (0.2.0) sha256=03eb98f2027429ccbbeb18556006dfb6d928b0557ad3770783b8e2f368198d6b rackup (1.0.1) sha256=ba86604a28989fe1043bff20d819b360944ca08156406812dca6742b24b3c249 - rails (8.0.4) sha256=364494a32d2dc3f9d5c135d036ce47e7776684bc6add73f1037ac2b1007962db + rails (8.1.2) sha256=5069061b23dfa8706b9f0159ae8b9d35727359103178a26962b868a680ba7d95 rails-controller-testing (1.0.5) sha256=741448db59366073e86fc965ba403f881c636b79a2c39a48d0486f2607182e94 rails-dom-testing (2.3.0) sha256=8acc7953a7b911ca44588bf08737bc16719f431a1cc3091a292bca7317925c1d rails-html-sanitizer (1.6.2) sha256=35fce2ca8242da8775c83b6ba9c1bcaad6751d9eb73c1abaa8403475ab89a560 rails-i18n (8.1.0) sha256=52d5fd6c0abef28d84223cc05647f6ae0fd552637a1ede92deee9545755b6cf3 - railties (8.0.4) sha256=8203d853dcffab4abcdd05c193f101676a92068075464694790f6d8f72d5cb47 + railties (8.1.2) sha256=1289ece76b4f7668fc46d07e55cc992b5b8751f2ad85548b7da351b8c59f8055 rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c rake-compiler-dock (1.11.0) sha256=eab51f2cd533eb35cea6b624a75281f047123e70a64c58b607471bb49428f8c2 @@ -2237,7 +2241,7 @@ CHECKSUMS terminal-table (4.0.0) sha256=f504793203f8251b2ea7c7068333053f0beeea26093ec9962e62ea79f94301d2 test-prof (1.4.4) sha256=1a59513ed9d33a1f5ca17c0b89da4e70f60a91c83ec62e9a873dbb99141353ef text-hyphen (1.5.0) sha256=c44a4533b8a554e7ff7c955e131bcccc78a0b4c56ce1d73f2c8c11f43b075a06 - thor (1.4.0) sha256=8763e822ccb0f1d7bee88cde131b19a65606657b847cc7b7b4b82e772bcd8a3d + thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73 thread_safe (0.3.6) sha256=9ed7072821b51c57e8d6b7011a8e282e25aeea3a4065eab326e43f66f063b05a timecop (0.9.10) sha256=12ba45ce57cdcf6b1043cb6cdffa6381fd89ce10d369c28a7f6f04dc1b0cd8eb timeout (0.6.0) sha256=6d722ad619f96ee383a0c557ec6eb8c4ecb08af3af62098a0be5057bf00de1af From 4a82a550ef997053d575b83e0d3595b488793d4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tizian=20R=C3=B6=C3=9Fler?= Date: Mon, 2 Feb 2026 15:17:18 +0100 Subject: [PATCH 043/293] improve docs for setting up hocuspocus on package based installation --- docs/system-admin-guide/documents/README.md | 70 ++++++++++++++++-- ...ments_real_time_collaboration_settings.png | Bin 0 -> 36516 bytes 2 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 docs/system-admin-guide/documents/openproject_system_guide_documents_real_time_collaboration_settings.png diff --git a/docs/system-admin-guide/documents/README.md b/docs/system-admin-guide/documents/README.md index 889a0ca7306..027c124bc9d 100644 --- a/docs/system-admin-guide/documents/README.md +++ b/docs/system-admin-guide/documents/README.md @@ -80,11 +80,69 @@ From a technical perspective, real-time collaboration relies on a running [Hocus ### Enable real-time collaboration for packaged installations -To enable real-time collaboration in packaged installations, follow these steps: -1. Download and install [op-blocknote-hocuspocus](https://github.com/opf/op-blocknote-hocuspocus) -2. Set up the server by following the instructions in the GitHub repository -3. Manually configure the server URL & secret in the *Documents* administration settings in OpenProject. -> [!NOTE] +#### 1. Install hocuspocus + +The easiest way to install hocuspocus is by using the Docker container. +You can do so by using the following steps. + +Create a hocuspocus directory: + +```shell +mkdir hocuspocus +cd hocuspocus +``` +Then you can create a `docker-compose.yml` file with the following content inside the `hocuspocus` directory: + +```yaml +services: + hocuspocus: + image: + restart: unless-stopped + environment: + SECRET: "secret123" + ports: + - "127.0.0.1:8080:1234" +``` +Replace the `` with the image from [here](https://github.com/opf/openproject-docker-compose/blob/stable/17/docker-compose.yml#L122). + +Run hocuspocus: + +```shell +docker compose up -d +``` + +#### 2. Configure Apache + +> [!NOTE] +> This part of the docs assumes that you are using the generated Apache config by the OpenProject wizard + +Create `/etc/openproject/addons/apache2/custom/vhost/hocuspocus.conf` with the following content: + +```apache +ProxyPass /hocuspocus ws://127.0.0.1:8080/hocuspocus +ProxyPassReverse /hocuspocus ws://127.0.0.1:8080/hocuspocus +``` + +Enable the `proxy_wstunnel` module: + +```shell +sudo a2enmod proxy_wstunnel +``` + +Restart Apache: + +```shell +sudo service apache2 restart +``` + + +#### 3. Enable real-time collaboration + +Manually configure the server URL & secret in the *Documents* administration settings in OpenProject. +> [!NOTE] > The secret must be identical in both op-blocknote-hocuspocus and OpenProject. -For more background on this feature, see [this blog article](https://www.openproject.org/blog/real-time-collaboration-in-documents/) on the introduction of real-time collaboration in documents. \ No newline at end of file + +![Administration settings for real-time documents collaboration in OpenProject](openproject_system_guide_documents_real_time_collaboration_settings.png) + +For more background on this feature, see [this blog article](https://www.openproject.org/blog/real-time-collaboration-in-documents/) on the introduction of real-time collaboration in documents. diff --git a/docs/system-admin-guide/documents/openproject_system_guide_documents_real_time_collaboration_settings.png b/docs/system-admin-guide/documents/openproject_system_guide_documents_real_time_collaboration_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..001d46972e4144b15619eb51b1f0b66efead67dd GIT binary patch literal 36516 zcmc$_Wl)=6^zYl20;QBvpoQY@?heJ>T>>rcDeh2;yF>Bf?oM!myM-cw;u?ax-1PT9 zb7$_{cW2Jbd0r+Ddp6m7ul4K}M=$Ogrv(}LE zy|1xH^HxCz*AJ|s@S`Je8fSg|t}uM8!bV;`;)~?Jr+}~Q71qBeUYwnYoZ{buY^p2% z-*;~ZYJ$T4Js+dKq5by~{pUXH|K3q&3!(m>zyJD9;lFoedp@Up_-~+}|L;H=xM5GM z)YR1dgM;W)Ilq4C1|VMDGd@4>OVFQHv1@yfe%kK>Qza3({+0L=bTq8bD$@0Qg}S~* zWhu9XhleNXw84bQN*eH#xMa0fTCNn(3%Wj@bQ{^)+KSM!P5Dm0yA5Neo9np8RNZm< zR2IPsd&|FG7+RbfrI%8v8*Gh&Nl5>o<957ys9UjhBd39kn0ubT%tUVj`lJu@yoAT4uE#E?}`cv-tZ^I{qnh-FAkwT|8JVt zCw29W6Bo>!60kl>8(QjO58oJ}%Riv=Glfm4p%>))2S3k!1|>fZo4(#&p{~ktko&Ra zB%sCldi8de)@hdq%dhNwmz71oZVb$)Q=6wOuCMEKF(tC@UKI5F#I0dD_VjS;TulN6 zr$!TUcjx@uNEg+^Vog+Z6ju+NYo`v>Dnb``*coK_q$I>^me9smNbY!~wf@lHg@ZEV zRL{HG=H;LD%her>{0A>qVU1S@yR5Z+%}}?`?Zx1s$MYCrEZWGh7@5_-jmDotXki_q zyF;79_s4=5%e#Coz%Gmh%C1UTC;MOix-JXJsl*{i^jsb)ef-77cR?nf^4(WjcS(Y_ zzqYqzO3`MM`-rpT&F7tec0M^CysKCKMAWeCxfP%nN{Utr-5vf!OCqK@WigCA7M7rp zDk(c^t@-OaxaqmdMk9u}6;4iX-`V9xW~>R^8Gb-=(@OKX7)xZTekJZlRvyw)R;O-c z3dmUTN=DC^v@x&{vs~CmCAkTf9@TAZfpshZaGE|lrXj}X-# zvd~ET=Jg7T$}}1Ikn|5Q{Si!Py;^Y@JYee_Zs(kA7ca0>X=dfnCMc48j4Wj4-uZhG zh#8!@AU%FC-vnIrfSvJ!Y1h}W+~I3@gKI6Gpi+awpE3OaMr}etKXqN*n}xf@4!q4Q zg;repotYxv-G%DIRNSiduVipzy6|J8KOxTZ(v%So}-YWyp`95~|?m7ex zjKk3J%8fuzjfcE$u*F^d(sa}51Y(fO_4$1|@!8qc@nW%m{6?XC62n5FH@q3XYH66| z|D`+bTfTfs>EFLwh9IN-QJpFiGdsJZIsE#OlYD1f}%5nqnKdpq@gW6vodixb{aB%uhhh)Au%S#QQpGZ(u84Er*Tzh{bU{Z7AIDNVoMjpWjCI zJ@BFTJw|n9es09qzriY2k-&PPhtGBe`L&j^N)0=p>P~g%CC4#WH=jV->>l3*Nagg7duVpR7uOKZFQBvwUcE2#te=k^jTE_aCxvb;219FzJ|KZJ?+}S z@isC-^m%OeBupS-I7ak@i+f6c0TfZ2jbgL|nI&3lH~Yrecm0HApK4g5J5-_m?Oc#E zBbU9S(OBypx;1&IncM5HiC55^<<3k6zeGX!t5Ap5!s1eG=rA^ryr13XK5O4ze)-pd zeaD$w_W&WL1!&*8v~W4D=H$(FtdTvG0L|$;W1{~+fK1qR9NVzX{2Me?rvg2<_Z7Ki zc@O*)_Xcqbnf~)#%JI&dge%V!LWW`~tFQcPTPnl&?Np(K?@ zYSXJLFE*E1K^~_1I2G*WzVTHvsyR4UD{K5UUgYQ!lfPNNO{c9%vZ&Zo(bCdV(|r44 zpg{QScXLwf+?;J=Q^uUmdwIVVs|sF#&!$A%o~9HR>zJ8oHkKuK`1(vY9nS*)Zlhb8 zG4XRzX(|{fyuo1JR-a+%vJc1Z=THvZ@eZAUqJOLzH8%^=y557b($k}rCWoDb*)9Ml zGC6^_3BhP+Xj!1EhrM;ZY6p7&04fn0|GS=x(hq zimVs%xp=+(yakW#wY9dkX!CJ(bu~|(%SL7@kkb#5RJtU!MAH->tP&RSyP&f&?j9O? zvy3>|#W*tWcP4?1%0HB5=jZ9IfU)0^Pop%j22m~#-u}zb9NT0}i@viR&dGDC1)h4# z0N$1U8Y%{s)2WWwj5}@Sbk;@~cRe5tC-$k+fLDawTE1L8rPYPaaFTy*J~bzVThShC zhjSX>bB2#ijNgZiBRuXnF3&)Z*7VaT(&;wG3#B-NjDbbWK!Q893MG9Bq^h z{QLt7b!_7Zdn-EoR6;3V7#<6LVrjCakxPZ$T18Z}u0B}JDk<_C+C?Qyce>{tQXk(#?CoK za97!zn(bpW!s5QEUV0nx(ltvv$9o?K+dNgq|FM5T*!E+Us_aVUMwntv20;NJKN;5f zWA8>X2q$c$M&=|{OU`Uzpnp_H2;|x2dCo(|RwKVHpUkM$J%osV6q-*OySH#Q^Q_ z)Y8-A7ZDL*quX13L{-TaN(`b7lViyiE-fm8v;hV|*Ca$jK4xFKI)`-rR_bvUaoNs% zma0?(iJ0WBOQ56`&HHF-YGTQRW-arCvvLCb`cN)WF!tPcjy!{KojdzEXVe+j^Fy-$ zPly2mVsWH%74j#;*DEX59irLx>6a4G-)F=xIIYj|C73-7UDeY(MM89-7X2UoSM`Hia#J6vv!G4cX@;kioU@Gw?DZ}OJ&U1|L-tSYXe+brI zMOCz8hV5w<{@$X_d%qu98i)S@bxuo*YhJ4pY?eUbk#otdkS5N}Fl=}UQo+)0b=iuQ z>z+7*dndUz&K-5mcRA=g$W%P*bmsm^4pRp=f3Um@_3cPuTK0X}f3{7a+hw)cY8mIf zuQ7_HW?V!a4AdOt5h#(Xbf}Y`o7vU@hn~VilnhR2)3EokkdOj{BU5ufk$nCf zG$ctxivNk4IEBn>A5vejMWQJF-f&5 z)nwXEX3)iMg8Uh^%6|NMjT9!Q&inj`Dyi*R$%K{oml~Um|m`M0AJt8Hi7Zh^T8M#Kd2wO6+u4M%O^8oy29rls8W(Rm#(;NvF8>dSpKx&ksTBSz!mxsJM)qraKLJsK& z`|YYM!4wu4McRyjap$-}bG2?Giv4l&C~kfuv`HfM&!3Bh`u_ewQNJ@yB3!s*YUpR# zAD{88ZEY1oq)g%&l2~c8oE+(NRlUfEh|!)OBxNlWWwfNEyw>-H{7;?sPNv@o6)9({ zcC$PmwW1%3-VFK*4$~W7H{R5s7B&<|huf;i+*zBVgoHS;h%%-*jEZWX!U6ysO)&_= zL`U4Zl(k2r(t(s~pH_EeOxo@G5U@L-0HkS2Zs35ZX{(F7dw&|7v(1|-xI!E5?7Kw$ z7VR3bTd6fxh7i7CsRKOD()Xxp8rrjKKAXl9BC)#SC+}kt^poO}7Af0_n4_~jPrV&v) zI}`MCK}PrACH>}5!2HaF&xlcP%Bsm?w`wtbyB-};j4rcXia~4-%MC=0Ar`gX*xI*L zA)cR`W6~`7TffUHf{MCOeb3E}MM4tb;BiXcg)kH)8laus$sT;ugt@a{X`b!=)WXu- z%!(MXH@T#Rj&w-`0Un;Wn&s+Jih(}2XHq)Xq&nCUi^+$)4ipMMnpNAw!dPR1i9eK( zC9lIa1MS8d33=J05gWbCCWZ8io#00FMt?+;_53TO_4W0A8jrIrJw?Tewzefiyg%Up z8Zp{4GZP;AkYpS2I;^_cLbf(#Fh#pm!Z41`b|i&lpB}8PI9$SxaU#peoTo9lc#ohmEoVUURlejoeWBXPdXY} ze%JKv@sW)hM){LiwRqXL5IUQEk`J}~w4!G82Y~t$W5TNCjQs^BoA9Zw4Rhry*L&Mzz6YJH33tfy|%7?cxpj z?!kO{z0W)W*R}0}?bgNy5jM8h_39%nrh~2312G*%n0|v9{zkya{GqO%evAJNpZP`$ zuETgbuYD8;FL1FbEPu0d*_~~>`^kS;SI5tQ@Zlcei~qQ#*s~0i|D2Ufxd>+$UDcMOsBF1}U{Z9}Se0m>C4xzTxmPc|s39 z&2U00H5i}YE@P64K!9^&Q&S(fe~iQD>ofjy$w*smBNUe??xl$IXJ@a*@;pHZXA>&7 z*K;{(*7vD8&78eex;(_4WPHf*k;ns8v}oFGJ-r`g;c=^V0Ia^JterVfP8r9`tmHk^ z9MBWcZesmrYUtOy&S8ofEH!vpp{IKWDM(>iFuDgtiFoX-%Ixde;MJ!?JCA;Dsu^W_|*U5HiSPMDKjd#2cg&e338(o#nYiYS* zlm+0nAndx*PBvD~Nrq_b(^ymC;BlLQ9r!8N0El6)sW_C2LVeDBaMX5wch8^^(`e+g zzLW97eA|xYQ(_gDSrx?C17JT=i9t*HX<|A(YilZ=hYK z!VQb8%+X@)r|>sii?d6QtcMGgSu8nUt*mOi?^6!u%fN$!Ado-Zk*cgB>sU5F20Ids zG)4c&2sSGrcLl2{zaJz&KAJ_RFz@H89|&O2jg+#lYO|Hsp&Rg&tBJ!$_#YIpnpe}MLXjKuw4 z8fc`K)fxdBc#z+20v98+#3umlE`?@kZ&}P4jat2wZiJI~7Bv#&L08c%H$ zGQP!gt&*7#u+kpQvpr#-p{4cdyRfd4(^&!i>?6{vipq0-zK;n}z@>uIz|h#F zq-1D1H`ZRN2STmVn)#7R0uy6Tfx(kEcF94?8k_v$f@{@n733 z*`uAzhKqiG6VL(Ul(=1p8$ZV@CQ@rVNN?+Y5@gw2)`QPcC%NjIOu-- zhs6JXM(P?l?VuiLb>g<2X+by)XG)HdeL z4Vqxxqqpd(VXSzp02XDk)rt-ECKGY-UIb??gOr#Wx9FywB@e2k6JVVpTj!{m4x}R1 z;k(*y8%Da_S}po|%0*jydk1@fZK2J-MWWNHGEF5}Ty6aM$^K76Ap%cru$b5C&h-tk zZz4{7>f8(>&g(bAbmv;%+5FZYZ{xI_M1TJt*NKjb%VjwR026eTT36$-`hx*Ne!8uL55=+Q;sB9{w3@$ZRJZ7WMdpz-vQ8vZv+91cp<%vzDHQUCVQy5m;9$s<{yXqW)*zG-GqJ)=Z9V`F6r(z$d<$j-@lhF!-_A9 zgNi^~tPgJ^Q(w1!i*2x(6~pfDK?t@JcZ7sP*hFiar%kdncJJeD#s#n}_UQ~_fcyFU``A9%PByqTbN%w3FjgG~Uu!Kqs1n%JlaqDVVHr7T4>ohj_Q5$56eUSJ51 zMUN$|!+T*OnIR=d#C#(=%V42O-yww-Q#!BT2XWADA*@!`HY7`3ApQA~jnN*Y`%UU6 zg1K@=Z)vWgQBX<-*Ec(;KqHvl$}ImS4AQ>86~0M@u?Q1%4@cU|mhmQL4^K##L741` zERjy@M*?R~&US9g)n1hx4u7Y)3;g>2Uy?F1#XvX-&Oo#Sbf(g&Frq)ANaVdOLRXl) z_~DMV%w*910=RCjI<*@R7>drX2GYhzO_&gFbJc6~84lTo;wmBT#=CWJr zn;eXC=G~OC1tg;6j*iYIGlWeiD)voof>dpeAEfqaIz9H~Ni=4212uS+-X-HCCh!2E zEdNR_(?E*BMQpAO`?nucNCK~{<{6CpkX-U2kvNN#Sd1#@P^KUvI+&n#n#o;Ifrkw&fM+yED5k0jc}S8F_u?mKyu}vwGtpi zdBx``(<>hPXBx7z<+tC;mFZQwJH*5QG^23;TFpwdK>&XyGtq97!lMyEQr8PKTH z%NPC>I8$i&`nIH`gp44*oyn;EPZiXAnhwELBBP^~Qnhs1fK8u=_*3a1MZT8r3|n0I z5pffJ*gF2h60>pL7xiaxxBI!?UPC89W?Ng^QC2Y7$xZm&SCrhpA&upB73YP?tsXG1 z=%5BuJ8%v8ew|GQq#8HJGmbHJx`cg`{bapQ=D*bAha-A-Q}GYZRqB$%#DC+hu^8CQ zv}?7|BL)6s3-H+FPN21N@AP^^dAydB3-?=ddVhMFCTjaREi|LqLBWWCkkJ2{vgw~Y z6yZ|TlA`XajQ*S=TPKXL;j(ySRo&kW4T*Xa5cQ!jAfU~M8z6-8@?`+N>qSlK3lf zX686{n)s^1kz~dQB)WG5ccHUC(<~>AhDcM$O8A_QA1V#pxBl~-)fw>&B!PMsR z{s6nyI&&^Au9^TkT8oB;h9T?txKHshazUAJ4Sd9}8wl(D;o8}5qWB6KhE9gGN&4YO z$N6+$Uu{jGnO9JH!fuV+KqTnLzYOVz&i`Hku@Nx|IZjqPP&cYoG6ntBuv5U`0r|D~ z%=h|Za>K(`M)tzpXi&<<~Z1T;!W4MrFAao0@{baxo8Nl#DbBWvA9o|~V` zD=2zCqgEf7;ef7AO-%t0Ific>?CdhlmN78iQEgK9cm)0M`x5zU5a|ti;aT=8+EDCKFcvO1d}+)5!ct(*O82m zifR*7LN$ymo15z|>Z5oz#eiUTVj_e*_E@F~h@&1$v~qv2NcoYzd@(UGk$O_1@4K#U z3tvTfe&Q?zePMk2!~Q*&PF1xl2>~{Pu#tL`%l7otSGs={*bDFb34_&m%g@3#zxaea z58laLH*I03o6;&5Wk|Jm4lOUEA~q&-`~E_6b9gv=+p6z_k9l>K!GiKkPj{(lDEKtY z85{s_QI}O#mg+QWaowN!RJ(;eqoXJ{3bbX!9a~sHoV70hCLwZivG%$W-RV5z)LWQb zr^n&IxWUHvQxoN%K2$0gnLT0JnmNbf} z+&&<^+F{le@-#Wv^b&NnqJ0V(Mj(=(ff6Ih%z==~>YTMj=iS4F^5rI1g;RX(52xGc z=|}w$0)SR8`-S6th<3o#g5h;(i(l}b_c&-+j z1-cGuKy~xIJ?rmX@qs@fZ2(ww@fegQ>f{*CcPf{e4Y)8dF=fJjY1O>WA5iwVdI&Bm z8i3dv!xO6Y>IK9R6h$_EYo!<=-pKVO)RDRQeRqfiK@TNb1sRAlI-?Z+3tZ_rYcHry z8UpFiS?lcZHu>6L*0=#e_|*cCWypWW&|TDJ^!}zcKfSF{TF~S0Dr-EQM>^DZmL{4+ z*ssof`Ta%MKV0RX5Pk?z_9d(C>@>Qx(PA-b`$?B4byzKW1k(hcA`myn(yX4CRfE&K zeuE1XDjG$cbN8znAt;_!YF<}f%`>Jn9sWVKzbKx}#w0qso^|RUJhHI4OOpFTK}u@8 z#R9SalXMJ4loeKC9;A&yRDB4UNxd0wuiI&s`Q=~%;z{{T|zD+KfGL4X@vnfD}f#+3EuG5Er- zu;w;tRYbi!VidjWQcA*1*Bwoz|EpwNSl&)Vwn&Caq;omY6+p|`ELJ2>kAKo94ghsQ zWfqq5-%zD0b?Z~b;ewI9XyG4j5{@119k6j2KQv0v-0_#yy^bYht zeHoIXVgGwQ8~-<);zg)|@9}m65@!E5YU&@BzF>3XBU!MD!@{E;7tab|%xV3n5vKR0A;w6ZxY8`1sRo_||V z$=rAOvORp{c)93l6I| zPgZA{EPm!U70d?p{Lmre^^7FHJlMi2f2`2gMN80)j{A%Uw zRiH3x%s%_~i^JyQ-mbsT2RBKKOli$7ur_88H7jtpaR-%dz`@i>XDYm zZu$OUo6u2LLn7a#RUPHd>s77AmfPWRUq`62tkKf)BCe~;2S5M)ro**3+GaVa1@(>Z zx*p&peubcC(Y}F6S$X9Ye=q!dsXNYlLHTCu!pz9+QCso{!I~zI&v{0!u=l)1>{!M)wC1X zH{E@TNJPCo_wOoe`|ifm`FbaKa!wK-)|xE0-YP^0;CkKOWo=Pk1*D|x1P)G8n;La` z?r!PI-VoEF$@jvU*xC2@BNq} z`64!E;x3C;sVc7LdRUmO!C^a8u(642GO_AK+C*fOe|3-PVC-I({WUqM=<~`z>r#`| zmU>*Y;IXg|0TF?XnblO8p2V=Vrj!)&eiI`v+^VEp$AuZ%riTFE_a|eH$m9_M4%>MX>oZVJ#eMd|KQl{bixPn?j9o?iXq8~yG&)$1sii7Eb%cN$Y|7-iXOpg}EuG*>YP9Im9Qa#O z++?G(k5;W_sKlyXs8t5@IM4_5rT6^eg^L{haR<+i%IN46 z+w?m*L?2pszmb#l314a2ojPoWQ&Q%i>+89||H!+IZq76s$f#stqM>neb6c<0yeGhc zi+U(#cbGvF^ul)8d;XN3*D$K_yc_KAzy8c-bM~u8IwT~+?a23XK$xF;aiq7xdk`{X zixny0wr6bMM*IcDmXj>CMnXKrB;tDPWM^o|E)%*6SYE$Dd-EVxjqsdy?njy6SivWV^CqtW8hW0=!dXEPn5+qaV<9<3p3R0XQoIyt3p>!&6Kv@BK{*fn;v%eNx_Ny(#m0dBi|5v?5GGbvkUR8@p0M7_K=4Fk?bZ{8^6u*~?1qvJ8e?K_NI zXs~wh>-p-1S+MFSvsM?jNKBKqeZ3W^=!jBqY+rHx`>Q#2ZAh$R`}r=;h=YIxksLBO z+``HtBDU^d5r$3TiTqk{fGWr-(;sWL$P`01CgdxtD!pq+9cijHI_luG{oeLq_IgrG)!iPm$Q z6bGR6qR77M%3*#n)h~B?z>7dhVL1wzy>x;3E*8K3@VHSYN43E16Z$)#CedxgY-|Uw zp_gz|>G|(*USEkkebAEcySqADRSZY|)t>n60pd=!+}yyz^*~Iq#4BNseH@!K1ys*i zMD2_1Wz(Fh3}tKpq@pkOtpNdySY0DKJ;!qotEoX}yf;z5z%Qdzj&#tzz9A*bMW%qep7JuM_)`Bs^uhcW{maEsUIdBY zE_;B!dX66Yl=2(-tJUw=Ai2V`!qY4~Qqr{=2aLJuW;pE}*1#s9w(jp%>uA<;TkihO z)3o#O!Fce|`Q|-6KfmY1#ylVCc#a4QlaSBwfs%fDdh}r-iQF-%(5VtzgE3}iaZTW9 zS`MGh?~5h#vB+jN;R#=hBAQ*6HQic=<+EE2@DJ>5dYvVFKn5>d$gtiz)LBjDw83e& zXLBd|;42&j8F`ZsprDbj*C#Mj-j~Z}yFT*1GGoIQCHjZCZSz-hWSHIQ{LgjBIAIFw z=1(~E(!kx)0BDu;R}hU>BhyApON$5o=ecLryEp9R&0Wv+ht8QPuWmEhv2O%Ez}s8Y zG5wa2((gSL>MB;9tXZ6;&N<&GQLeCyd+-7;UY98PSi~uUZ-N;ivFTjJ7V~fGX`@}9 z_kw#jC4wiVwF&7uGMNRhMZIrvcTI3M4Oct;=tT9^&~@zCIOYcNiTZA05W)sysajrM zuSmqH4IPnEuJ-mvd;DFwIoMh}V2~)}(Oo6tKrPD(IeX^u1Fez#F*LOBKVzGgEKN3$ zFVf?GBS}M5AD8CuE{{GF5w%&ISIs5LE~REhNyxrpB}OcROCS&AN;s4U_D9t7;&(;U z-SjNpjrQSmsgjsrh7J(AV7D61T4N$b^ntI#^Z6yl@2Q%T-;?B4x{;0|(s}EVoFB_x zp$9YcV@AX^1Kw2ZUfEU%4Vkt=ssb!U7mmstGZT3u=zHYbzxU zQE-#ps^vQLN?gs$@Or%BN#oA%10Fuzg~ehT+_RK=Bnmn}2i@Ykp@5FQF3CI$swU++ zeKH=;p0mqO0EI@|MQ|?@=1*#Ttt3Z~8Ijte13HCe<{C{5K|yJ0%s_c@vkLXX45CO_ zqE5~rfP#^vh#6uhI%n09CE(v`9IFhuWrF8*ZT_uI=+834BV?z=)ylUCu7Vl-JsH5F z9-`WPNtu7f>BluSM<{qy9nOhoZvc{|mN?{}#k9~KG|;xJr=*=+KSUzmYaLyn%8|+B za)XKT{C1~RaIQxv>2bYwo9Qt$lQ>p!Vc??Y3+m=NE*UBEvX6t7uDEyf#qx`!)Sd^% z1I-zB__nOX{9V3H$0A$Xo4k%@OOAdQ7acMxg&?-NZLpt!_p%wZE&+~n)9xOw?decDXxqHC4TUvO<^9Q&GP@#sQ9KTXwJ=3K6D!| z%U-cc*_!AB$*ebwn;3&zl5gW;^LmAFVP$bll`xU~2JH#kwj%W|SLCW2=Ox;ck9gh^ zODkJ#utL^`C%hH)sd3%Q$TUmct(ATJ6g9gDMgP!CN9+yN&`a{Q2u{{Jh*Q#*oPZ8J zov_%zWRlQ0iH{D!E=Ph7*5574{C9fX?z4?8C-Cb=%dfLnj|h1rMfvcEI=Ow=6v6y` zy_o{e4CiBQ_ek_2#uK^yEKLK>{TdKzz2I1A)T4C+A9tf)4}t?FBMKF8o15!z|E_28 zjNkwGJ$b`3Zf^D?(WBBPxoYjs%b1Ko^TQ9LSMrQp?rT#$sj~8gRgGP4K1te{} zR62NMc8$Z9!5&z~i8nD|Z15>xelAv&|8(_ifnVi$DzLx+T4>vhj$0!Yicf(1CnzuG zJugsHme3y^T@r0N3Cu$w{;4i7zu0+s=SE(lcA7qh+QnveY^L`et#RgNT&@Ifdt6GC zlS_@voWTc6KHGBA)=iw`#abn2@LGxIM-of7=7GyNT`)fB8YFwQlY>W?Gd;?0y_XC8i`E~ z(NWRw)J*Yhv02B@f2Fw62f#nit_*xmvyaXKxj?)wB@82#?` z0zp(KN+CRS^1(Y&g=gkIW>v?3O3}H?HNR9dlo3O>_>`ykBveqE4z-rXzsKfuG}Mn< z=B(`upX(q3` zHnlPFNKY_VdaTOk%KCRM#JQwEj4Z*C$`aku&E?bPQEWajHJ(OH9iHw;D;*E+Juzy% zkQNpBeON*VGaZ#zk7J$;BN3`W^A&S)lGBw-93sTvRjq&>e{!OUyt(-)W|(eYd&=tO z8>+43*aWtuHN|Djx5NCrn4ANXq0GwkW8(~76|p0pxN?YoGTPri*Ev|Iih%1#4$-|+ zc2O~Y+SEt4dy_uINA=y~;}9^K#KF-=+cG!DsccNU>5}z+874Dy4h!5QD!;$ygHIg@ zvdAYr1zEIZa6HZxC(b)ye{!t3KDihW4{A8Kgz9VM6lDoHdi)LuaCg4AjF*=~D>mNh z4#nIH-sen_F;>Au?}_OftVpoMuhskcip>8fCfOAkPl3!Xgkgjs4T456d;7FRemlD6 zU%XYB0em#R3t>4rI{5l6%;8B2V3^~Hk{?crpHXvt6*|leu=&^sHjw3h!y(mAUGRo} z$YTr9XQMBna(>t-VO1JyXV@#yY_#3GgYd$z)_oS!LJ!-`!UbZ+W%m-ZSlv7~cE+(M zV#ZHWgR~o*CB=myXqom(WuN3ah7TaiFD8Ev;oHkoUeP(&uN2z!1iFhx%u+^jRJvM& z2|vd^#3t7smkt_d_zxa?N3;?Y_{uM*<$1JTavuur_DK`B?UE}8l3DuZFl-+w!N%Xc z<|hKd$%zw(HU-m#>&%x{#vA&n1COp0hq0v?bm$@zd6dohy!vr(;8LrouQx~cyQgQx z^^K9SB|g*XRSb=TFw?n8f`vnI+RWq`(Vd6cyjo36OxS;LG1^qZYpU_r!9kM4g&IQ# zJ5P|q@Kvi!tGKI#iy+M8mj%qH;g*tjC~ z`39fFLZg{`#FT5$PX1P|gk!Cvtf%Pcs6v4!pX+V?{Zg2>-7i;VB zh3uU_l%+I1=$ce0CQ}H$c}-8xPA|aI^rNzo4982|zVdlB^P#D9Z8jh$(7Euk(A?{d zoCE>h<2IsP2%x5+mgAu+cb%`$7CW1n7$CscB8O#MUOf(qFJYibMeqeomvL%Rkfzmm&d$;Xt^^FIb}VU)COb(BrOdzTr_^g3mB?mD_!2zCmuCh<9|?!+ z47cmXZ<$ilE|S`i!!)$3wLc9j%yHhCo8v8CYkX0L7mr>2hzHy*`f<$+@1t^RBqZkv zeyLNRdod9dcGBN`A{BEUa$#6fTZCb&lHS{a zrpEf&(Hc;|*!9b`DsHGHZ11K*JA&s?@cG1He#`fVm^R~MTboZS3WRmZ;~4)nm9Gtd zit?y+`x!=LxC#^d*$+M?M02T!8orfn2?as?*lI=8c>Fy{*q1nK&tu}YT%cBzBxDZ) zdQtrC9&DYrfo;p@>suf>{6Hf>z<(1SX{(VT8xkvWV(52pyLgIWHkmMMq0+=*e{8X| zk)Tz^w8XiQrqPC6m0y_*le@>G^4C?j8ZXKs6>1l1_mTgoB+Q-4bR0B+k{NiS*AGgd z=iIS;X(^T&Us&xH=d&|EU*GysY@l6lymPmkz2O$+d7cQug7fy_~zd}uUwvK#%m6n6RXuVBUciw=a2@taE_$FLiG)sT~MU!eA4=r zvFf}HCEJ9vVJ|5`XZ4R(viC8V7DaQM&nR-%^A%!g3H{WjCxzJ#1Q#({WIW=GccpPjQ;v=P~W1eut5pQk~SYXS*&YRdKG@ z(}95ysZ)ygx|AY%u{mA6x3jgXFWVWP7eenDoNpcq79EqmU1$1yv0u~D=Is?ELV-AW?6@ikLAcb_ku$Zm;IPTA5HrDc@gb- zb=*qAXbL$UI5UvK+@gee`Pq;lyI8(quX_Dba7{@6k``Uf{#k~b0;@}vIvZ>?;iL1v z%YaXvnnp7*GdH_EkoG88oyKG!g$WjCQX_ApEGP?QPy)K)Nh@2($2H$IQJ9wK>?8nx z=rz6xvJli$&Je~g6eYLxavH7Zr>@tmTjC_(=eK@N-|FD#;Y%f7dQCfNBpoAnbS9QA zVn0TmDIfPEMOd)6Won9TPH5-T^9*49(RtT5-iuHX|I~;=QfUTwdvjatBiQQUb!(!w zKcN@KJuMey*7RFtaecb)m;)QPk@ARsu84toEi4?~{|#kRTr82ALa~?~|o< za03qMM8-yrt|qm4;TEaK3sr=7{bpklWvA6vzJIpe_~z|aqFlUI4XLZCQnlUEtrlqUvRs`9sbFe-ke*`bwI0v8CZ~#?f;FM-} z@!i@hD+vWWY~Oi&+P)iv7!J9Fdv1(LnUR}i25-f~O4Wys^Qi_II-fJt;y_nh!V@^y zRE<>kluV}1bxTV7B9}AH!}S>vADd*DPLJjZPjM@-r6PsAZ+9*Q-Pv_05&M0putj4K zGCq>T&}jmN6ucu>(pClhf$~v-Q-7TbzGskOlz{&onkwNtHUBC(RyeG(do$l$6?x5S&W-ilSgmOIy86LkJ!p@A`r8sO_{-e$DS~DiIc|ulV@8MywRG zM)h~5tHnn9U2`tnTGWhEirb%v=)DVP#-bYrd7M}0nhy?~Sxs>g-lh}Yg-UH^2Apmf zXERmd)Q{#HgGctZD64TWJ3i!vQKIX8?mSb1z8yYF$eiqwyyHxswTLCV)~RVwG%)P# zn$5mt-unB~+T~ir<3Uj|IZPgx)#zkjDNmajbS{d!w( zcA%&m!&NqgPRFDj%2^;HY=RJ+jec8R>UttkU-IRgsotK5>~@&J&77qRC$qgKk+DYuuw*`+mDR^W?@9D{wE)2SlandVRu0l zTqPQh8_5}nf=aKW)8p0S=mLslOQ*vp3S%FLMn`swz*t|RCxTf;*n8avuhQvB&O<=7)wVK_0i4)sc#P?Kw z2-~;_JE>oI6m}wmakIc7W>M6p(nP%;f`_pHK+$X@IbszX7q^ThM*C>r zRdZ!?B*beo-H%D9TLyI+qw3>?>V&o4F8U_bCC@h#y4`ihC)Pg}CDvbU@lRk$Z)CDs z=UZN4r7+JzllF^^yjVQlG_XQ{l4P&$4KQRwUWp05_O!^v055y~quP){`$*RKX>ZJ& zk6V=53yAvDccH(s%bnLX9w3F77TmGGf$J&e=GA){vY_rC^_6ngpq0C&110%o#Z~v` zJ6r!CQd$yEDapZI$bzJV&jN;fgYv1<@pM;gDB?;3R^L}D+zmVe=3)KUL4#w$cb~91J<2tbe=b*bF1sK04R*8guHJNANyRaJY~~U1nWPk8!MaVMrp*bK z>K`>K9TFzn81ltji=$I~MqDf~K~v&Re*4}6nfaY}H7JBVJBO0B>q6phyw)}st8Jv# z*Y$+m+op2e7wANdn^(47Y)qtuUK_WwEIJcO7nTgg<0N9o)nBdCnp%Q#ma=NS&fGgAPdmat+8N5}5xKG!3(uX_IH9%>lY)X+Vo=r}bng>V9JNS~j*+x|G2*Zdc z^BBm@D*SYBo#agCS{OAJCgQO&L1R3rY@bpErI>Jf7%NoF0VgOA@{GHxnpU*4xk(g8AssYH6Xa)iaEpVK64|gZt06PRIq8F zw$lfe^6L-Z1x8r-8CIYdaIw|DH!6686UW=#p4OBZ(YTAKJyui|HF?Yzm6TW^DwX-6 z7vmQ%!9$!4R_eg3JT1C}EPIiUT26^p+T4M&;FfQXO>lmni#cR_W~GaJRQcvAc zV_^Y1sOtx;k4pjwh>;G`gs_ITC4E0wt(^~`EPf^GKd``Dlqw{+Ob_$qnCS0XwfF&q zC@eV0WhVr{TZKcFeZr&W7G}%0^xNe7>w4Yy*UD#8vQTTx3d|8b{0#fci6OGqi%ZNB z1ImnAz1CmgEc>hW^9?;k#P{DR2H0a`EcazL!Z?Hz3dv)a8huBV1g|!QVqz2##0cVl z|Nk`i) zGc|S2Ox0Jj|4Naq&(poS*IM^|HMcS}5Rb$2`G6XUuB(G3x%OD{-V>o2Wx2XN$cBHf z*~Eox3YKxdPEkR@Xoly2{GbqXRCdHCMwX`Rezh-j%aL$dVx4^_sYS}UIFtTl^i6iX zk#<&lld{JrKh;pN8=FCKT{wSpEmlHZJx&UHyfalPmp>LUN&7-EFOO=Yx@W3DH!)!` zjM8{r;#W;8QrV}a5!(9Kf%uC$cDap#{}uq$qLIIAp&Vh(*B&^^#vXswkKX@$Kv6$c2Z-P~A8v;X0;MMlBBzaDek9Wy3BINk{bMnJoEoJD4pR&xp z!I0Qjj5yg5!1^HQQD2!{`%&mscB|d(tYmhT6%aNgG3Y;TY5<-1gd@s{8U89=^JX(p z2Q_EAdh2L2L4*d!S^vX&z)HpOvc=EKvIY+`{j8uT?6SFvbCWtb^KlNY;c*%Y!$zE_jEfz zIb8Y*#bcY%Zzs5J9L8h~5?3wJvHng;4Hhm;i;@zb>DJCQ7z17tZaob%$_NJt^r3DU-kNr(6n^=iwBX%zFEEcM^!AbbY*R6fjFw#~S<)B!pVV zk}y#fNbtfEVPePny4)4r+R#BqylzM_IF6ghrRmF9&-4nirQM+z5gGJ!!59?snK%L(Y?O=&B&@GFW=e>VZ8qYK^|QCmCKVzCt7qAloRO8N$m-` z$M--SR`(pPPf9uR>7Nr5lZuMIeS1!n91vZ^Vy5b~c1ZYOFc}#-8>3tKcXURukVMtq z@OsafF{B#rgKMAx9LH{=c!bqV*4OHgp;PSYVI`eRS6tgKz}&D(dU?Q@5EH`&gpE@_ zChiR=s+GTEY*9v9yntR(l0gqz6BJ*~5pCC@J}fIOEq#lqOxqZA`}nac60B@Z4l@Li zB9QGC7`&tw)b;85IUTPvN=j@3-}W`=O<)<2^hOlV=UMszm&wO|;23GI4RwVSZ~&{v z@i=B?uTnc1i`C@uyl=n?OP_KINEZs0tnRUx6F!=>gS0O1oCzfP$>z6E#> zma|Tmhs(r^t~Zwm{4gx@QlH}2`pnmFm(!hvWR-ymzsYmeNPkr`k)xu#vh?*!cts^S zq$LhJ=tbFjzJ)~rF){I!<+T09=Fy$SMA{Kb)<>aC@!ATr<*ckMH1v2^Ih4tXT8$y# zXQ?t-tdLFp!s&E?%VO_i5an@&_!@_ci-Dn=cPT|}tP|tYax3W(E#sL<(mT7>c1l$@ zpwU;RU2u25aXrWVEGt+RI-*ZrpxOA2ieq+onD;{4_UPzBMMFa)>HEaQgbIzuc(oIv zqV@hf*{u5Pj&kwtXztg)Bb2^-Mo6GoozZIJG+N8e9TNy+auzQf_+ zNRURAR5Fuo+wVeKTiZ%AMV)f9*s?M^NlBWx1}5d@1N_fkv^4#9+jG>>VMrXiK$QO7 z-R92f89eZ1sWsWLmLZso-M!Dh<>2GpekPRoBc(C8L@?pw-er(zx&8ihlfIuL#>wx2 zIck++UmqX;a%X4PRMw?pYinFBU;=9DkF9?5=Y_>fdG4vA1z9;c)6txJmso_Fgv2Lc zl%PnBjh)%8Jwe20|1CeNrr9lmI0Ur&Q-ul|0s_i^%9%{M{Wmu^MM9D{P0WF&U3RdR zpC8~qFuoGl^tRh3)}19Vy3yr57=DN@(bs4+^sKCuYf8ReWEU5cW8UpIa^BMJ$s>Dv zhdMGpz4bU+A-xr1#=V;?)WG(B{CxN#su8x@NUCJ5e1py(T@VJwBdP&dhWvqbzi7P| z)^y71#yB61@a$(A8lAR!75~KeTo(!oDa>?5lpCyl6EhIYYS~`W5@UGy)3edd5#=GY-r}-**Z?yUCbD=D62Z08zFx~Ab1rP@17zX?n`oUJDR{fYV4ul~WS69MjLbMd~jo?g) zdeN+k!RUj9t8)Y$9i}U`OPkJP8fcdK8b5hakZ#-VNG*#V<=>~GC zy1JqSfq3lK$G*(9cI%g55=;%rxKnd+tpEWvMveU+*xP~Vp=}_!xj~(!65s?hQ8x7u-iIT!o^LTtu)J`OHJYC_XY6<*s@lT z85S8W>kY~)D_fe;>2+Scgnt|0blJ}iKaE}@@t!m-3&@p2@OWk_-HQLt&wxMdh%bfB zIsc=#_;A1R3JVLXag^?AYr2URLS;OdRIHv}1Vou`=ZMim$#k+mUzJUgS4B)DQ7;v)^;yy%4*0ak-kFRdsW_B7o(nu~x}K%}g)Us&^b{6A9ME za3MdcW1^<+>kPueVRxM`RH~YeF41E8OPB`Z^+)?%sb@xqBSTpNgQ6u|!E3Vr(IiTPxXk%57~V9Sq8xUeza z&&zu|^_C~D!~z1ga-=fFX}HN97#c z^c=fPSkYSG@SAOGLs=d!z*JxuZAl$oOw^!Ow(+=Iytz0E=i^I0iVCbl#&0C$esp3q zgfZP*oa)`O#cXVyWH^Nr;^m{{@zx9a_{gUtu31!H!PyLtz)9w7Vn- z1d>%3m)az4MeG6WQJaWF2jMjQHI7wFAN#1vNDs*xFE&@6C*^DLkeR&oBR!SBO~~HMURLn zSe|&;)j}NrC;}t=4HD%W=~gz>RiD5yCh5U{o`gY6L)0s(BNC(n_Xg<(%Z9(x6qDTe z{(FT00s}I3a!P(7#9QrpAwj4_i)k#P5DEC}^Wwe- z`}O}GduVNNhYoBYnHGPj@OfNc@`wqxqsKq-1$-#-YZ3s@v!Pp$+tL8LhV418(7zM* z;3~5XGL-xkfYrb-?}4rt%V=oStBnZ*RuKPdTux8B+g8#qL<6zdcdy6{bfAJl%sDC8`PQz`j#B>o{M12dpftaOe(J6IE?)PENL$ zxnstyg31sb9zcG)-N05CG;3>wfIWb4v!&XCd$3ci|WUW7j!>$Gi-QV``lcn z?6L>lg9PZb>hyGVJ^%DhxH_1xt^=nLB=tVjw-zNBgbGe35gy0QAArlPeam z`>d(CU`+oOpb#9-;L_WEG?p#dV0StT9N3@MdzA{!3|VIC?GKdFoJ-+0r1j34pTkd1 zjBL%3SLR+`^eBM9TA3dOP-IdGTn-@OgNhM4dZq4I6y8D|4o}%qU*8K_tq3VixTwi; znPwv?>=0b;R=Varz&y&9j%PRT9EXrf%mYJU`=%{$VXEfZ3;nsgbl9Eun4gxbG@s;U zcK!KFS6Mkq^frb4gK01H`)XH&9p!ffdkRI9o>rICR9)cmLcpimnS6~I`58vL*%ofS zw4%Zp7_U?teRub;CyR{@O3ham6*vHb$cNeCBQyrLo^l1U|1-ITFY{CaBdQga( z-1$6uDZH|}9&ve`&HwVb{T*bk015Asj+qBeE#epFw*`?#t}Mu4(uTF(IIGG#-hL61 z8yNu|plCWBA!?U}ODPO|6qJCo^$JRqbbUQYIAmCI2$1P`Z0RJEi83%SHi?aLP0pNe8h%+#@3s_mPcH0n-yj8Qchjab@Jf|TwDza6sqMBi*OXEDQ8 z0KUR{>i6-)j@vmtFUs{!fs1kh< zjxBw1#NPlP!tgxl=Iit8O5-KKW{E%27kOpZ4aImf>ew|dhVn*M*ZFc>>=c_-psuHr zV@XEMG)si|K5|mdhiJF2E4IstsBE-+^`qBm4E_hGwBgxgAJVyVd=PM;sdMN3IvS z>xPv36h&+;bl*_d?~6<_ceG^Saz&tD=EdIJ-s2P``*XbBcO>p$uh)cY6uO_S z&%}*&&!`dg_7qo@-v?e;Fpu)oNl&eQm?=^7cz*e|(syjC+vA6bAA-xXwQ){KLvy{H zE@Bd$?%{Ge>)^vkNJyA`eFOGI=H~ju8Qi|P?(5r4O`agJ0$>r_t23?~dwSoBdSWiF zMl^b@;O}8Mv8=|PAy!(7O3g-|gGtTKYn!%rpFe}jhjxQ|ra=8WjIC|;!KM_C=Enl} z)hK0pSQ~(IwRlgE61gwEAMP_B`^b%8@`c5?DMhRy(R24h)F zIEtf)y(BynUY|-qt1bZ@`I!ErHwsd5Og9T|PH#PXD`Ej0qq=l3dNmTwfgeUsTrhIF zf_kbb0B#6s@?fhL9r1(`0pWC(S;e=Lxmno`W~%v>xEL^F`rGyCyK$}yWLcx4w;2iCN4fwS6@ zzt3$V?$p(_ZgUl%t0Z}C;nwREw1HH&nQHJoQFP^pN?s5lM(^aRP-7%hUWT)9yY z=^u(2;Sj9g$!ndb&(m!~?7ON4b~@&|N{fPal!p?VZ5F;sfsM<}-FA>G2D@JOW+@8b zE3>jN6v#E2rGGo^@HKFS|7?mk_U&}XVSALAjpSL_-fpZyvwo)4Ks?d5`N||#sdA~F z^wX>JMkk(AX^3Qq@O`Qw&qnIvIdrw9F{B4m=)3fo-@mF*Lp&+$lsHQ|dHn#t#O{yY zvKyh4T!BF>GkJ(Oo}W+|q5ij+<>kisCrCZZ?>X2}kdS)LP|8S~v0R?ex}cL@52r!G z6%Jxl25dUs_8g)(;omQbH*~i@OV-ejw!-hg{&IX`Vt}^)9)V6o)Qi;y@e%^;AP~^P z$YU9-DysUIaQ||sYWHbf&Ia>yJU)PI%9E4}eAo!SKoL2;X}AL20swrqm z0mi~5F)-R-Z0|ht^FRW*SG4zkNYP42s>3`r-T7rrd_bgaev}YutKQk8_V6l-o)Pztoj1nxU>l1CIqUhb1z zXx@xOn5w%aJg8B3b_t~0UhAhC=PmkKXINZkdsm39UMs^#%%F`Qfft^Tz-64*x~85%3VeZUSXscWI)euM3xcP z@h@fuC$YS}Q)EGZ2mK+tzeNPM!ym!k(2(>xpJ+l=MM-I)Z09F1_kmPe{av9>r5<}A zczK26Nf9thNBwrWblmwEMP3a}E1IwQv^(zWPp@;l_U=!x%kO~m9A5^D`z4%CwafX5inW771!tw@ zzGi`_p5ACDt{Xm&M=ap~6EqL_dpGj)x2agp@2~C@XON5+sJ9f_a`%ja!5Eyx<+Z#$ zuo)AxBJEr6WRjVc1?+g2IXRm>?Q*70!wU?$kr5G#4OceD^Xou;e}>0FsFeY{yjv&Hh`S4z zz6ZnR-Fd!ar}hr6uW`njl)|5i$aHjeM$;;#xpgV4@&gRmS)-rzqDxM8{z8p*;nyx1 z`Z#vJ(2!)WpPhSGT#fFhQ2SXHYsF4d*4yuaKNc%I4$`yfy;z(faSdY&rNB|I3NMQIaMn+6RPfXU@p|Kw4@De zb~qThZ6oLL^HEoek;3IPIQQ0Pb5>_HCx|iwMs?t@8mK`jZPe=luI14^sREO`Fa{p-i2yluT)2oA0y=r3_ z^>5eHGw#WH_x-o54wb9f^e;`lTN*OWIjXFFRMkN9^!4<0nmns*IxnVcE?zOC0nJ;M zDv}@2Gc~(xeDJzd^+AwOwnkoS`b9&@?s8k^5VSqX+YJR)y+NR*Akhu6bsXAYbvrAYO7K)=e^;TFh_FWd% z`im4S`Kjc`5AiH>7bsMyE|J28dUX-lWcPuhRt_;|Pplkmy}HM*x8)*e?(A%9 za*-@KGCSnphl55($urxRX9QK~rBZdoz&tJDO7{?HZUuPk6CuG>%BQ{SVG^a~J;onX z&e_?HuqaAx?>^&I@`Cm{e7J7KI(ZJsHXUQigKR^SNA)TRUcYIEqQJa!sfM z!U^%eOggR~8xE5Q>C{Vokmy0C=|4%4z+*>&CpI3<;G=!}bCRPtvUVom{lRfeocl4N z`{4zn$fKH9@o2>^?uP=S;BSW1AA^E+Y<$PDaC%T-F!Hl@VJDnU6rxp0l+5M z7nb14`?3AuRFU#4Ul~zR=bOn!e}QceT&I0*J99XpDvw_c-NK^texm-R0s47TXnYaQ&LGF;g8^7z zdc=^6FTy2k@&a1iR8;Eph4||BXi~Foh$A$?-@l_?Ww$lE_ZVKV(tB5BO%nzB`?D}H zh4veo9G;g%`qr|2UK4P4Z(UOh=!gauO;s}WQi0p;s)NH^t*a16i!u0xGk;yMaZo9m z`}Q*z_~1q1MA1-@v5ZVE9C~Wu5oD=o6zVEQnW8k)J|`mZ`-0HjHKa3HtcAK~L}D4r z!*|6=#eqfZoNjI2A}l26j^uX&78k70j9SktyvwT&xMe0Lrt!P_BoqaG^6~PFL&$pe zSiS_d8>faX20Kh?4<0fWotpagH*Czbs?z0Y7JXu3LO8w4XL}cv`hL4S?skWpxi5vV zrt&HA9zn#JS%yV!`MT4rh?@klHhNuAm*iP6oze{A0^OKdxb(W3h}~Y}qK_7hP^kgj z0%@7X$|G@q#W@g1kS;$sAI}%|_)CT)*nhg;JdJp5`L!Z%<(ms`n!IOa)Mn7v%o^iy zy<8cu?F$!1ab|tJXX#;B-Fp@)XMD#NqpXP*D2c3dVr;A9e93Sdm>)u^Yb@-2*emLad2Q= zphdkgr!9jBZrQS}!l}6!1vT`4g@8#0|KgJL(NgkddTt8ghrEOj}Y~HHdH$Q zw+OuZI6z4`C= zMa6n8E2OBDftaO-O1XF1jTc++vC$YI#T&??AXad z5O>b;x!pGQ!|Iydd32eW6~dkHxn%P(K5fm?+M0q9o^gnIP8Q|9I}UU7c+%hNgP)Jl zyuq5-8@~)?&zbQ}(lKHY*Guqo3E!apv($%st>YHX$$fPL>r_Xl8l$iQNP<>Kn1<-wxu|M?F`g_=CP)jnOJJBe!X&?_pP|S zN=lp)9n!Alp8@BQ0F(o)X{lhg@<91)k)#COKxya=0EpB(SdeBcV1*?_Xx4-_UMO!= zSphW14yVPFV^r3>2y~53HPhLWcUbMBFkr7O6`Cu&e(dS_SfFcgcvv=HKD8h_x|jfe zHywx{3zy1X0K+eFThqd9Ky1Q3u14`vUH!rEk!ZZV*&J8(ciZaMh41#}i*r+xx>%}J zE!q=ys&`*s-~fH(wq}2>1*NfV>`RB|K}kQnIasOmyHn!Ke5pdyFBw*x zqgph+YFsQV8Izfwu2re0wid&n)|p5_c@4zLV3P*u0f9o~| z?}$E_P*UDUPoWMf4M2n7es^|izpB&hvIA(o>*A;ljUJ$rg^L5L)#&IkvjP)IrTpf8 zSol-;tqnv;CccAX6yv*kGDV-amSa*uF>mQH62`|>)JsP7?t*~ES$t3Mlw4su#v%cfg)m?Tr2b|HlvEPMq*^jeo zZH~&Itw>n9;v4csz5yIlxl7KgzYA9`Ko#k}6<1lKT=35En#5M_4LbUJuVsk1lKBie zoiZp=E9FnUGL24f*d4vut3Q9qj+2mpq$$+ZPJl+GRFnozlM0Hj6dKzp-(?1Qo0OB^71*)Ei45tLBn1KHE0TIwiuCLQBvdN9s)(}*K(fezuG+mgD9W_^_ z+HuD*@iiU?%_|8#ef{&@dG#vEEpUznvDy(h9V9S;&8olY#t*pQLGvf(h=|TW;?P+bx<#R00aw0e6sT;?OEFAkyDO3=g7RcaHhA$hlk>`C^C(9O{p*y z6lVGY^%TJQ{aWho&asI8KCM4K{R+qqg-FKIH8uddkciFYd=b|gFs5O=Ae7*YsIOlI z-ej&-mhnqMl<3Urm?8($B)0Elt4eZB8r-uBMd|vIJ`dz zy0iZ!c3N)l8T+(Be+-54<2^k>T-=_m)!!PWgHRTptixSiV?JFW)y2fb!20oYWfzbN zC1Y8%kF9{jiIDT9P@TgT$!kQsX_ki80qTb;iBRq zV^LkD{=B(7!(+Bu@#z_()2svc^5$}2PMtMB2#Znj><4I42;lqOOnBKexhp4fR7E%G zrvFcBqwNWuvx{nxsll$`X5>q%Ia!FqEJvc(Jd24rPi$XOc&ZY^XGQIxz1B8 zia6@DZ(8=2KhiOE83T`McH zhlnyVM0Nn9D9VPzQIf6u)wQ#;_Q{hcN{UL`<3*+>9|`oMKy{WbfhQZZ4ngQk=Jw)K zP)OIDVVWs=c@*+hfRIGk{pQ0u6BEQUAE<-8z4?H8^6c=iKqGtdULS;vaHi77Vfkzr?_NkV9_$guke)Q&lPqNGoC-H@! zxRoePZx!XjoLo@wjvE}?DdoT_;E^~leEFcc5nN1G2$T_pLgD096j+BRMrHMbef>ol zSCEn0i1lrxtE##S2g?~j*_m<;L1NLxb98E=H?ufB4V+q8K=TGEjOgLWH&S0gK_S7; zX^nUw0TT3jR{JC{W%wnuXMJ=^NkKthl3y@`m+jT6Vjv2WbRQf0KTpyNEb*rx_P}M; zrsWlcn*V%-`5$CGx?&dYUy7=-?yJo32~oCo_jCF7r5!0RC@MPdp#c>TOt#^Nw_)I~ z7!7p)ccOBISS}a4DioM!ejAMM`nR<}asf8h7Y=oJYK+_Uc)sUTG~*&yi0Kxj10)rtS3emWB&DUd#;aTm6rp968j$et zrCP59ZkO?ppMM`o*`2e)meE5CAq_v;^}SFP3b)6OT0u(X{cS-`O85?>SuNzY z51;aXJW#=cAucWse%poCb!jz&-2N4FG}l}hHy2) zdeH-e-8Sm5@ii__xl5Pl-wwhOxwR^j#dY8r_Y0?O=C_3#iC4iSq~vgR^)Bb$2?`&8 zV^`u^)}P{^JOrCx{h|+x|GhJjBFj?~3rekfuJ91NucBL4t;D9{RAb*5ZxP(>Xvt@byvvqZSn6eUhoN;jR(i zh2LKWiu8m9jW5z3)Nsroe+G;(=Rdb!HzpTeJbm(&g_V`F(sWo<6Oz-65#qGRgN2H!FrRBPG-%1fGEOSt ze7n6@#qG!6)>_#V`GRkxZlIn~TScT^Vea=}$B&OpGS*8{bLSq=V@8%??8RndZR&sz z0-fnO)RWc&#)jJ1*(-Fv8Zt9$1KS@+dDf3 zv@5n3ULb;e8L%y9X4JFyBMQz_ED)2qmOOr`+L)+n!=3CWs&7=X4Aszsf&(7Ai|6rX zF=cdl{p2t!A%*O+5hzPB#jmrW#!CQb8}M zg-|#Doz?l#Df)(kxh8#fS0q#p%FafzN+;~mn`GYpQ~Q3zj@)G?_>zL{j7j8T?Ol!O zjk@#qEQ6^tTFYa#MK7qu$)JZx5x47QD-VIm%zV#g4k$ONsHosi*Y~Z>1Vj(Ul4LCK zm45tF_ZwkHu}Jm|xcsT#TO@Nh`Wq~Bb5wd>?{-Y<-q7?P`|IOzd47vF0cQx+uaE!f zD~x9qG2VP8puAvHBa}u>yKs}qSwvaFI7Z5dxnI0D=R{Gll(L-@R z>VM_T?lm)eS(&HX6VpFE{W2V!l7r9X=sKFi2TIN=){8<5;r|+j(610fxA)onm~>0$*Wb(Z*4F7d9gia=QBGJVD$|; z<#m~v6Xmh0>Ojy3@I=Bd{ETRWI6AfNGuYJ!0lGb)YTp}4MX3BXV@}dy1QgaggEm8) zkqKL>7W~LUFlpQnd*8gda5>FNOn-h3oMq>r&NU4KXB^mdVGp315ZlG>oXti*8Ja|H zF%c1LzhtplIsufsfvXUS4awJVh{+trxrLQVJuA|n`wEtK?RQS{_L4MDsE7sHwj;IZ zHEUh&MikyI%IffNtw<=7a@qb)^YDy6YP$jhk#I5GQzB_q9CSV|DL4SUwNoo$@1d?f za+i;z+0N>*SO(C^g#0<`r8w%D7+;IbSze3~2DQU*#E;Mh1@Fn+dL!cTs37~eE2C{i z{kk22+j?ue__sR};X(citM}XUOOuy%5fs}8_&$s#6rAXv1V!q9l-YsK%7}b)5?*QV z1h$hLL{m6M2GP<)P*w_SoIharUp}A;*dD8oKLAp=O^Z2h=HdLB#___n8tUHbXC6me^89x>WQO4TBu2Q@x=J}qR zMR(#=h-P1e`{AYnG1?*zH8UMCx zRHUR3-0(+&ofXCwUW70PQnl{$#*yX+RWiI63f35;NH<@E%l~WjaVxxcw&cpiqyxDjwJEYY-nL-qOxz zYQgm*<0mt?OzIsi`1lC2bVWu+4A=D6&{0b;+Y3=GpAJT5(!+tL$N*TJO&h%J3CN#$1%2NzM zu&dobJ+C0^+W6?UNMj9F*6pQY0xUXz=*Pq1CPR>yyapNm((9DXq9apM=H8{@@)+)h-uTJ9#^Xj!64T*{L}oqe2WzGk`^bfCE{hhp#0S@Xs@; z*=K8ja09aSRiKS#K$7wEdintlj6^jNEv=NnqY~9{&D>}zF+9wKQbkqkECq3amn4*U zM6jiJRMGUG_D3s0Ut_9_ZfpO$Om%FpIOYz|95;pNRMl)X{3G>^rsQkVRR#JPCMI#P zAE~B)<4xk_)~z9LXQwzm71a6;^jh)${P^27`Hr4S2o9Nxy~4ry5=kU1ltjYD^7eaJ zalUAN?D(8RYis7z7Yb_jb#oRX4>feNEQt9QSS;yv>V-IJAR9HvV!=d&ZFIteB1yIP z+of9`4=*Ff2@Cd_S{&%!v(UNv^lY zrIl`oJW{KurAc#9y*8m6|;_Zb$mEMRnZ*Y2^Ax4itU@ zfD*M)%@^wYfrxHLM59c}+!erBbiZpskq{08CPhojW21R=01Udhe%cP#R;1ZX`@s*Z z91Sos8v|A!?}5*i}N3_H%e6oji{AhVRQ{#T}qu&($xWmL%vMK zixteE3=h&BFfX>Q%1N_ogYs1<64}>$y)RJNsYZjDv^lGeH;m(%!P#a|h3d=cO}<;iMmi z#e7J9t{N6ADk(CQIZd5mjAd#C(AI;~=GetVLH0HPfQq^KsX<=vEtmXI31JxRZ15Mn zw7)8X?2g3@(3@~@aRY>HLZmH$y*FL_Pe=ayQ!FmWj|X(p@}+)-m60J!*-2kJQzSfj z4+dAt!BJ}z)FBw-=3swg5`u+|dA2vHz1t@eT^pNgYM>aADRc?vTvuUUYcv+T__Zr` zy+;c`>lmfGD->g;DG?C#ST@~W+apXY%M5WQIrb))%#Fw6}mjWF3D+B9bzhXICT6|FQYF1r+8jTSTsI%lnMW;6@ zKaD_(<(n*`&uHV`=amCO4LC(xzgUT4V`U@Rb*>+DZ$g()Mv*zWIjbLTdL$HOSrF5y zG_dx~8JE|eI^_vimuD#VIoIZ02qbX)SuJ&`&U zIsuoxW8|TKZyi9o5q6flLi~gKmBSjp8HlI?WX3KF8I#CB#rIO*{Z2>|${EMt!3Xx+ zA<~5%@g4W-Tz^lPf49cZ0F4}Y$TNH+eaJI2KEg}fKga*SnP+0O{TJTI!&nt$6hzlg zKpW)Q!{=TOfb->hFn&-lupvD~-g?3y_<#qvlvUA?=NTC{nexEy z5ol=h*SfH%{&|UT z8KMLMBV+E-_I`2O2Ux6Tvrjy99WBFWCdmi@&LM94&(Dye=yR3{6bq$N^E8w&OInK# z65|zkMJmWHr(Qb!Pc4NkqCD}*z;$_XQI>Z7X@G(XKYKW4=-;nqRrX3E)Lm3s+z}Ca zUAa%*6Oo79`+ZcLn;K{?z{B(DjIjjBfpykuHD>tPT@)Tncq7*2eIk}XDuizctf47 zTdTb^clYRj?=OD}N~;-7<46o6Q5DK(GHJFiS!>I$|?M@LjrIzj$ZXuLwADSFhM zgR`Dhq9*qI?_XN~R6%8Vm}&SVM>h_g(fiV-E_X3COY_5`n$`;Si|H&}oDY+R6iDP3 zz0PRNlwT4fHgj!+&JA{UBe$&=YQeLfA~v$+7pKnUsotztSFJ4U;GG+5YRXYmTZVC+ z{d3P#J&$P*ramm@m*g3`ZIC63zdKN?8~C+4HrQAne3U5h&RU@%HPXBGTV7qN7LEK$DhrDvKU3q3V-5tC&+M!;3BFy zIXPKgiEH5FwTqHwj#nFLs=F+#CK<$fB6t6WsbM)eWlBpWcwj$u zb2}>j0V&IDiZv(`pdUnI{$#SE+9xV;cNkEPaY8uF$;eJw?ENP`B?Qt;Z!7n&w==5B zNR!HyCXHV>cR|Czv@ra*n68*Ffxk~eM(*nS$28wuX+URx!8pXT2p68kfwY`}6d4DX zqe8x#JXzZV{QH-)=1b%x;B}?e;llcr_620&HlW&4tq`O z9GiLOFv2hg9*$-srwAC$>&q>Azh6_wOAEom5O&Sh*B2ttcBqA7u%B(~p`%wjIGu|| zF(1vZ42`7HCTyS4EO4-pt;CH_Xg&-==!TrQm{<=qD1rqf&aYk_`DRbNh4=Imfqo-z zjI+^}lG0LB6C-(;%_H;&8%xL153h?L{?GBW$CcM+wh@!^LPhG&xxcoov{>vvw^GUg zGfGtb45K@lGdVPd=z0ZNK~{Q8@4ku+b_JZ>w;ON4fkCsr)Nn`5?yAViU+l83k2^>n z=Dj@#ArX8+wKBerzIHRPw#_!-JajM*A?R=4ck?{V15R5_OiW10f$zlD7v92Wb4_R8 zj4pzl1Jh98m7KKUy}9-Nlj?hpH{J@Zgiw(Ij3zmGM^{&IW98NSjye9924o0|ug>SV zGPB$HKW6NT+v~H!T3WcVxS&FH8%lJ)D2*JDUX?t%BO|8Z`v`#~_&g%Dq*$M-@q4=L zS5^jr&G;=Aq12?+((!k?AX9P5M;!^eM8bdj&p9~=&sLbNI1HI2(>Hv0(Z8Wn zL3{~a{hH%E>Tj%q#w(yU!L2_2yZ)#(G1L4d(p;+jKch|f&qIdspDzjXzbW Date: Mon, 2 Feb 2026 18:03:44 +0100 Subject: [PATCH 044/293] move automatic subject creation to SetAttributesService This avoids having to reset the subject first and having to save the automatic subject in an after_perform. This is especially useful if no change is performed at all. Without the change, even without a change saving would lead to a new journal --- app/contracts/work_packages/base_contract.rb | 5 ++- app/services/work_packages/create_service.rb | 13 +----- .../work_packages/set_attributes_service.rb | 16 +++++--- app/services/work_packages/update_service.rb | 14 ++++--- .../schema/base_work_package_schema.rb | 1 - .../work_packages/shared_base_contract.rb | 38 ++++++++++++++++++ .../set_attributes_service_spec.rb | 40 +++++++++++++++++-- 7 files changed, 98 insertions(+), 29 deletions(-) diff --git a/app/contracts/work_packages/base_contract.rb b/app/contracts/work_packages/base_contract.rb index 9e176e846b0..5a25d556bb4 100644 --- a/app/contracts/work_packages/base_contract.rb +++ b/app/contracts/work_packages/base_contract.rb @@ -34,7 +34,10 @@ module WorkPackages include AssignableValuesContract include WorkPackages::SetAttributesService::ProgressValuesCalculations - attribute :subject + attribute :subject, + writable: ->(*) { + !model.type&.replacement_pattern_defined_for?(:subject) + } attribute :description attribute :status_id, permission: %i[edit_work_packages change_work_package_status], diff --git a/app/services/work_packages/create_service.rb b/app/services/work_packages/create_service.rb index 9050a940fb3..68d7461cf22 100644 --- a/app/services/work_packages/create_service.rb +++ b/app/services/work_packages/create_service.rb @@ -56,13 +56,11 @@ class WorkPackages::CreateService < BaseServices::BaseCallable result = set_attributes(attributes, work_package) if result.success? - # Set attributes service passed, meaning the contract is fullfilled. + # Set attributes service passed, meaning the contract is fulfilled. # Avoid running validations again as we might be in a project copy scenario. work_package.attachments = work_package.attachments_replacements if work_package.attachments_replacements work_package.save(validate: false) - update_subject_if_automatically_generated(work_package) - # update ancestors before rescheduling, as the parent might switch to automatic mode multi_update_ancestors(result.all_results).each do |ancestor_result| result.merge!(ancestor_result) @@ -76,15 +74,6 @@ class WorkPackages::CreateService < BaseServices::BaseCallable result end - def update_subject_if_automatically_generated(work_package) - if work_package.type&.replacement_pattern_defined_for?(:subject) - Journal::NotificationConfiguration.with(false) do - work_package.subject = work_package.type.enabled_patterns[:subject].resolve(work_package) - work_package.save(validate: false) - end - end - end - def set_attributes(attributes, work_package) attributes_service_class.new(user:, model: work_package, contract_class:, contract_options:).call(attributes) end diff --git a/app/services/work_packages/set_attributes_service.rb b/app/services/work_packages/set_attributes_service.rb index b0c40bef6df..448d613c862 100644 --- a/app/services/work_packages/set_attributes_service.rb +++ b/app/services/work_packages/set_attributes_service.rb @@ -43,18 +43,22 @@ class WorkPackages::SetAttributesService < BaseServices::SetAttributes model.change_by_system do set_calculated_attributes(attributes) + set_templated_subject end set_custom_attributes(attributes) set_custom_values_to_validate(attributes, validate_custom_fields) - - mark_templated_subject end - def mark_templated_subject - if work_package.type&.replacement_pattern_defined_for?(:subject) - work_package.subject = I18n.t("work_packages.templated_subject_hint", type: work_package.type.name) - end + def set_templated_subject + return unless work_package.type&.replacement_pattern_defined_for?(:subject) + return if work_package.subject.present? + + work_package.subject = if work_package.new_record? + I18n.t("work_packages.templated_subject_hint", type: work_package.type.name) + else + work_package.type.enabled_patterns[:subject].resolve(work_package) + end end def set_custom_values_to_validate(attributes, validate_custom_fields = nil) diff --git a/app/services/work_packages/update_service.rb b/app/services/work_packages/update_service.rb index 36e73d6cb07..28fc88c747c 100644 --- a/app/services/work_packages/update_service.rb +++ b/app/services/work_packages/update_service.rb @@ -41,12 +41,16 @@ class WorkPackages::UpdateService < BaseServices::Update private + # TODO: check if this can be removed as currently, only the subject + # can be updated automatically anyway and that is handled in the SetAttributesService. def set_templated_attributes - # TODO: code smell here: saving the automatically generated subject depends - # on running the UpdateAncestorsService right after. The subject gets saved - # only thanks to this. If the UpdateAncestorsService is not run, the subject - # is not saved. That's an odd coupling. - model.type.enabled_patterns.each do |key, pattern| + # Subject is handled separately in SetAttributesService. + # Other templated attributes are set here. + # TODO: code smell here: saving the automatically generated attributes depends + # on running the UpdateAncestorsService right after. The attributes get saved + # only thanks to this. If the UpdateAncestorsService is not run, the attributes + # are not saved. That's an odd coupling. + model.type.enabled_patterns.except(:subject).each do |key, pattern| model.public_send(:"#{key}=", pattern.resolve(model)) end end diff --git a/lib/api/v3/work_packages/schema/base_work_package_schema.rb b/lib/api/v3/work_packages/schema/base_work_package_schema.rb index a42ec221d0b..a0308918e3b 100644 --- a/lib/api/v3/work_packages/schema/base_work_package_schema.rb +++ b/lib/api/v3/work_packages/schema/base_work_package_schema.rb @@ -56,7 +56,6 @@ module API def writable?(property) property = property.to_s - return false if property == "subject" && type&.replacement_pattern_defined_for?(:subject) # Special case for milestones + date property property = "start_date" if property == "date" && milestone? diff --git a/spec/contracts/work_packages/shared_base_contract.rb b/spec/contracts/work_packages/shared_base_contract.rb index 9d38a5260bb..aa0b534eece 100644 --- a/spec/contracts/work_packages/shared_base_contract.rb +++ b/spec/contracts/work_packages/shared_base_contract.rb @@ -81,6 +81,44 @@ RSpec.shared_examples "work package contract" do it_behaves_like "contract is valid" end + describe "subject" do + context "when the type has no replacement pattern for subject" do + before do + work_package.subject = "Allowed to change subject" + end + + it_behaves_like "contract is valid" + end + + context "when the type has an enabled replacement pattern for subject" do + let(:type_with_pattern) do + create(:type, patterns: { subject: { blueprint: "{{type}} {{project_name}}", enabled: true } }) + end + + before do + work_package.project.types << type_with_pattern + work_package.type = type_with_pattern + work_package.subject = "Trying to change subject" + end + + it_behaves_like "contract is invalid", subject: :error_readonly + end + + context "when the type has a disabled replacement pattern for subject" do + let(:type_with_disabled_pattern) do + create(:type, patterns: { subject: { blueprint: "{{type}} {{project_name}}", enabled: false } }) + end + + before do + work_package.project.types << type_with_disabled_pattern + work_package.type = type_with_disabled_pattern + work_package.subject = "Allowed to change subject" + end + + it_behaves_like "contract is valid" + end + end + describe "assigned_to_id" do context "if the assigned user is a possible assignee" do before do diff --git a/spec/services/work_packages/set_attributes_service_spec.rb b/spec/services/work_packages/set_attributes_service_spec.rb index 45f394bec9b..84996a8014e 100644 --- a/spec/services/work_packages/set_attributes_service_spec.rb +++ b/spec/services/work_packages/set_attributes_service_spec.rb @@ -2286,16 +2286,16 @@ RSpec.describe WorkPackages::SetAttributesService, let(:type) { build_stubbed(:type, patterns: { subject: { blueprint: "{{type}} {{project_name}}", enabled: true } }) } let(:work_package) { WorkPackage.new(type:) } - it "assigns a placeholder value to the field" do + it "assigns a placeholder value to the field when subject is blank" do instance.call({}) expect(work_package.subject).to eq(I18n.t("work_packages.templated_subject_hint", type: type.name)) end - it "overrides even a passed subject" do - instance.call(subject: "I will be overwritten") + it "does not override a passed subject" do + instance.call(subject: "My custom subject") - expect(work_package.subject).to eq(I18n.t("work_packages.templated_subject_hint", type: type.name)) + expect(work_package.subject).to eq("My custom subject") end context "when the pattern is disabled" do @@ -2309,5 +2309,37 @@ RSpec.describe WorkPackages::SetAttributesService, expect(work_package.subject).to eq("I will be kept") end end + + context "when the work package is persisted" do + let(:work_package) { build_stubbed(:work_package, type:, project:, subject: "Original subject") } + let(:resolved_subject) { "#{type.name} #{project.name}" } + let(:pattern_resolver) { instance_double(WorkPackageTypes::PatternResolver, resolve: resolved_subject) } + + before do + allow(WorkPackageTypes::PatternResolver).to receive(:new).and_return(pattern_resolver) + end + + it "does not override existing subject" do + instance.call({}) + + expect(work_package.subject).to eq("Original subject") + end + + context "when subject is explicitly cleared" do + it "sets the resolved subject from the pattern" do + work_package.subject = nil + instance.call({}) + + expect(work_package.subject).to eq(resolved_subject) + end + + it "marks the subject change as changed by system" do + work_package.subject = nil + instance.call({}) + + expect(work_package.changed_by_system).to include("subject" => [anything, resolved_subject]) + end + end + end end end From 7e75b21cd9bdc9ffd5492ee35e506b2004fdc929 Mon Sep 17 00:00:00 2001 From: OpenProject Actions CI Date: Tue, 3 Feb 2026 03:50:22 +0000 Subject: [PATCH 045/293] update locales from crowdin [ci skip] --- config/locales/crowdin/de.yml | 28 +++++++------- config/locales/crowdin/ru.yml | 16 ++++---- config/locales/crowdin/zh-TW.yml | 38 +++++++++---------- modules/meeting/config/locales/crowdin/de.yml | 22 +++++------ 4 files changed, 53 insertions(+), 51 deletions(-) diff --git a/config/locales/crowdin/de.yml b/config/locales/crowdin/de.yml index c9d7c7f0539..a2bed618d2e 100644 --- a/config/locales/crowdin/de.yml +++ b/config/locales/crowdin/de.yml @@ -1507,7 +1507,7 @@ de: even: "muss gerade sein." exclusion: "ist nicht verfügbar." feature_disabled: ist nicht verfügbar. - feature_disabled_for_project: is disabled for this project. + feature_disabled_for_project: ist für dieses Projekt deaktiviert. file_too_large: "ist zu groß (nicht mehr als %{count} Bytes erlaubt)." filter_does_not_exist: "Filter existiert nicht." format: "stimmt nicht mit dem erwarteten Format '%{expected} ' überein." @@ -2446,7 +2446,7 @@ de: baseline_comparison: Grundlinie (Planungsvergleich) board_view: Erweiterte agile Boards calculated_values: Berechnete Werte - capture_external_links: Capture External Links + capture_external_links: Externe Links abfangen internal_comments: Interne Kommentare custom_actions: Benutzerdefinierte Aktionen custom_field_hierarchies: Hierarchien @@ -2502,7 +2502,7 @@ de: customize_life_cycle: description: "Erstellen und organisieren Sie von PM2 abweichende Projektlebenszyklen." capture_external_links: - description: "Prevent social engineering attacks by capturing and warning about external links before users visit them." + description: "Verhindern Sie Social-Engineering-Angriffe, indem Sie externe Links erfassen und davor warnen, bevor Benutzer sie besuchen." work_package_query_relation_columns: description: "Möchten Sie Beziehungen oder Unteraufgaben als Spalten in der Arbeitspaketliste sehen können?" edit_attribute_groups: @@ -3308,7 +3308,7 @@ de: label_journal_diff: "Beschreibungsvergleich" label_language: "Sprache" label_languages: "Sprachen" - label_external_links: "External links" + label_external_links: "Externe Links" label_locale: "Sprache und Region" label_jump_to_a_project: "Zu einem Projekt springen..." label_keyword_plural: "Schlüsselwörter" @@ -4302,9 +4302,9 @@ de: setting_allowed_link_protocols: "Erlaubte Link-Protokolle" setting_allowed_link_protocols_text_html: >- Erlauben Sie es, diese Protokolle als Links in Arbeitspaket-Beschreibungen, langen Textfeldern und Kommentaren darzustellen. Zum Beispiel %{tel_code} oder %{element_code}. Geben Sie ein Protokoll pro Zeile ein.
Protokolle %{http_code}, %{https_code} und %{mailto_code} sind immer erlaubt. - setting_capture_external_links: "Capture external links" + setting_capture_external_links: "Externe Links abfangen" setting_capture_external_links_text: > - When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + Wenn diese Funktion aktiviert ist, werden alle externen Links in formatiertem Text auf eine vor dem Link warnende Seite in der Applikation umgeleitet, bevor sie die Anwendung verlassen. Dies hilft, Benutzer vor potenziell bösartigen externen Websites zu schützen. setting_after_first_login_redirect_url: "Weiterleitung nach erster Anmeldung" setting_after_first_login_redirect_url_text_html: > Legen Sie einen Pfad fest, an den Nutzer:innen nach der ersten Anmeldung weitergeleitet werden. Wenn leer, führt er auf die Startseite des Onboarding-Tours.
Beispiel: /meine/seite @@ -4618,7 +4618,9 @@ de: project_mandate: "Projektmandat" submission: description_template: > - **This work package was automatically created upon completion of the %{wizard_name} workflow.** A PDF artifact containing all submitted information has been generated and attached to this work package for reference and audit purposes. If you need to update or re-run the initiation steps, you can reopen the wizard at any time by using the link below: + **Dieses Arbeitspaket wurde automatisch nach Abschluss des %{wizard_name} Workflows erstellt.** + Ein PDF-Artefakt mit allen eingereichten Informationen wurde generiert und zu Referenz- und Prüfzwecken an dieses Arbeitspaket angehängt. + Wenn Sie die Einleitungsschritte aktualisieren oder erneut ausführen müssen, können Sie den Assistenten jederzeit über den unten stehenden Link erneut öffnen: description: "Wenn ein Benutzer eine Anfrage zur Projektinitiierung einreicht, wird ein neues Arbeitspaket erstellt, an das ein Artefakt der Anfrage als PDF-Datei angehängt wird. Die folgenden Einstellungen definieren den Typ, den Status und den Empfänger für dieses neue Arbeitspaket." work_package_type: "Arbeitspaket-Typ" work_package_type_caption: "Der Arbeitspaket-Typ, der zum Speichern des Artefakts verwendet wird." @@ -4877,8 +4879,8 @@ de: other: "temporär gesperrt (%{count} fehlgeschlagene Loginversuche)" confirm_status_change: "Sie sind dabei den Status von '%{name}' zu ändern. Möchten Sie wirklich fortfahren?" deleted: "Gelöschter Nutzer" - error_status_change_self: "You cannot change your own user status." - error_admin_change_on_non_admin: "Only administrators can change the status of administrator users." + error_status_change_self: "Sie können Ihren eigenen Benutzerstatus nicht ändern." + error_admin_change_on_non_admin: "Nur Administratoren können den Status von Administratorbenutzern ändern." error_status_change_failed: "Der Nutzerstatus konnte nicht geändert werden aufgrund folgender Fehler: %{errors}" invite: Nutzer via E-Mail einladen invited: eingeladen @@ -5283,7 +5285,7 @@ de: hashed_token: display_value_placeholder: "***" external_link_warning: - title: "Leaving OpenProject" - warning_message: "You are about to leave OpenProject and visit an external website. Please be aware that external websites are not under our control and may have different privacy and security policies." - continue_message: "Are you sure you want to proceed to the following external link?" - continue_button: "Continue to external website" + title: "OpenProject verlassen" + warning_message: "Sie sind im Begriff, die OpenProject Anwendung zu verlassen und eine externe Website zu besuchen. Bitte beachten Sie, dass die externen Websites nicht unter unserer Kontrolle stehen und möglicherweise andere Datenschutz- und Sicherheitsrichtlinien haben." + continue_message: "Sind Sie sicher, dass Sie zu dem folgenden externen Link fortfahren möchten?" + continue_button: "Zur externen Webseite fortfahren" diff --git a/config/locales/crowdin/ru.yml b/config/locales/crowdin/ru.yml index 0ab68f29b27..5dff2cc6f08 100644 --- a/config/locales/crowdin/ru.yml +++ b/config/locales/crowdin/ru.yml @@ -1270,9 +1270,9 @@ ru: port: "Порт" tls_certificate_string: "SSL-сертификат сервера LDAP" mcp_configuration: - enabled: Enabled - title: Title - description: Description + enabled: Включено + title: Название + description: Описание member: roles: "Роль" notification: @@ -2558,7 +2558,7 @@ ru: edit_attribute_groups: Редактирование атрибутов групп gantt_pdf_export: Экспорт диаграммы Ганта в PDF ldap_groups: Синхронизация пользователей LDAP и групп - mcp_server: MCP Server + mcp_server: MCP-сервер nextcloud_sso: Единый вход для Nextcloud хранения one_drive_sharepoint_file_storage: Файловое хранилище OneDrive/SharePoint placeholder_users: Пользователи-заполнители @@ -2635,7 +2635,7 @@ ru: title: "Пользовательские действия" description: "Пользовательские действия являются ярлыками к набору заранее определенных действий, которые вы можете сделать доступными для определенных пакетов работ на основе статуса, роли, типа или проекта." mcp_server: - description: "Integrate AI agents with your OpenProject instance through MCP." + description: "Интегрируйте агентов искусственного интеллекта в Ваш OpenProject с помощью MCP." nextcloud_sso: title: "Единый вход для Nextcloud хранения" description: "Включите бесшовную и безопасную аутентификацию для вашего Nextcloud хранилища с помощью Single Sign-On. Упростите управление доступом и сделайте удобным для пользователя." @@ -3026,7 +3026,7 @@ ru: instructions_after_error: "Вы можете попробовать войти снова, нажав %{signin}. Если ошибка повторится, обратитесь к вашему администратору за помощью." menus: admin: - ai: "Artificial Intelligence (AI)" + ai: "Искусственный интеллект (AI)" aggregation: "Агрегация" api_and_webhooks: "API и вебхуки" mail_notification: "Уведомления по электронной почте" @@ -3412,7 +3412,7 @@ ru: label_journal_diff: "Описание сравнения" label_language: "Язык" label_languages: "Языки" - label_external_links: "External links" + label_external_links: "Внешние ссылки" label_locale: "Язык и регион" label_jump_to_a_project: "Перейти к проекту..." label_keyword_plural: "Ключевые слова" @@ -5395,4 +5395,4 @@ ru: title: "Leaving OpenProject" warning_message: "You are about to leave OpenProject and visit an external website. Please be aware that external websites are not under our control and may have different privacy and security policies." continue_message: "Are you sure you want to proceed to the following external link?" - continue_button: "Continue to external website" + continue_button: "Перейти на внешний сайт" diff --git a/config/locales/crowdin/zh-TW.yml b/config/locales/crowdin/zh-TW.yml index b456e37fd92..e305c560193 100644 --- a/config/locales/crowdin/zh-TW.yml +++ b/config/locales/crowdin/zh-TW.yml @@ -111,21 +111,21 @@ zh-TW: link: "webhook" mcp_configurations: index: - description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." - resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." - resources_submit: "Update resources" - tools_heading: "Tools" - tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." - tools_submit: "Update tools" + description: "模型上下文協定允許 AI 代理向其使用者提供此 OpenProject 實體所揭露的工具與資源。" + resources_heading: "資源" + resources_description: "OpenProject 實作下列工具。每個工具都可以依您的需求啟用、重新命名和描述。如需詳細資訊,請參閱 [MCP 資源文件](docs_url)。" + resources_submit: "更新資源" + tools_heading: "工具" + tools_description: "OpenProject 實作下列工具。每個工具都可以依您的需求啟用、重新命名和描述。如需詳細資訊,請參閱 [MCP 工具說明文件](docs_url)。" + tools_submit: "更新工具" multi_update: - success: "MCP configurations were updated successfully." + success: "MCP 組態已成功更新。" server_form: - description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + description_caption: "MCP 伺服器將如何描述給連線到它的其他應用程式。" + title_caption: "顯示給與 MCP 伺服器連線的應用程式的簡短標題。" update: - failure: "MCP configuration could not be updated." - success: "MCP configuration was updated successfully." + failure: "無法更新 MCP 設定。" + success: "MCP 組態已成功更新。" scim_clients: authentication_methods: sso: "來自身份提供者的 JWT" @@ -1241,9 +1241,9 @@ zh-TW: port: "埠" tls_certificate_string: "LDAP伺服器SSL證書" mcp_configuration: - enabled: Enabled - title: Title - description: Description + enabled: 已啟用 + title: 標題 + description: 說明 member: roles: "角色" notification: @@ -2412,7 +2412,7 @@ zh-TW: edit_attribute_groups: 編輯群組屬性 gantt_pdf_export: 以PDF匯出甘特圖 ldap_groups: 同步LDAP使用者與群組 - mcp_server: MCP Server + mcp_server: MCP 伺服器 nextcloud_sso: Nextcloud 儲存空間的單一登入(SSO) one_drive_sharepoint_file_storage: OneDrive/SharePoint 檔案儲存 placeholder_users: 佔位符使用者 @@ -2489,7 +2489,7 @@ zh-TW: title: "自訂動作" description: "自訂動作是一系列預先定義動作的單鍵捷徑,您可以根據狀態、角色、類型或專案,在特定工作套件上使用這些動作。" mcp_server: - description: "Integrate AI agents with your OpenProject instance through MCP." + description: "透過 MCP 將 AI 代理與您的 OpenProject 實例整合。" nextcloud_sso: title: "Nextcloud 儲存空間的單一登入(SSO)" description: "使用單一登入為您的 Nextcloud 儲存空間啟用無縫且安全的驗證。簡化存取管理並提升使用者便利性。" @@ -2877,12 +2877,12 @@ zh-TW: instructions_after_error: "你可以試著再按一次 %{signin} 來嘗試登入。如果持續發生錯誤,請向您的管理員尋求幫助。" menus: admin: - ai: "Artificial Intelligence (AI)" + ai: "人工智慧 (AI)" aggregation: "合併" api_and_webhooks: "API 和 Webhook" mail_notification: "電子郵件通知" mails_and_notifications: "郵件與通知" - mcp_configurations: "Model Context Protocol (MCP)" + mcp_configurations: "模型上下文通訊協定 (MCP)" quick_add: label: "新增…" my_account: diff --git a/modules/meeting/config/locales/crowdin/de.yml b/modules/meeting/config/locales/crowdin/de.yml index c26545d3a66..5590717b534 100644 --- a/modules/meeting/config/locales/crowdin/de.yml +++ b/modules/meeting/config/locales/crowdin/de.yml @@ -236,15 +236,15 @@ de: summary: "Besprechung '%{title}' wurde von %{actor} abgesagt." date_time: "Geplanter Zeitpunkt" participant_added: - header: "Meeting '%{title}' - Participant added" - header_series: "Meeting series '%{title}' - Participant added" - summary: "%{actor} added %{participant} to the meeting '%{title}'" - summary_series: "%{actor} added %{participant} to the meeting series '%{title}'" + header: "Besprechung '%{title}' - Teilnehmer hinzugefügt" + header_series: "Terminserie '%{title}' - Teilnehmer hinzugefügt" + summary: "%{actor} hat %{participant} zur Besprechung '%{title}' hinzugefügt" + summary_series: "%{actor} hat %{participant} zur Terminserie '%{title}' hinzugefügt" participant_removed: - header: "Meeting '%{title}' - Participant removed" - header_series: "Meeting series '%{title}' - Participant removed" - summary: "%{actor} removed %{participant} from the meeting '%{title}'" - summary_series: "%{actor} removed %{participant} from the meeting series '%{title}'" + header: "Besprechung '%{title}' - Teilnehmer entfernt" + header_series: "Terminserie '%{title}' - Teilnehmer hinzugefügt" + summary: "%{actor} hat %{participant} aus Besprechung '%{title}' entfernt" + summary_series: "%{actor} hat %{participant} aus Terminserie '%{title}' entfernt" ended: header_series: "Beendet: Terminserie '%{title}'" summary_series: "Die Terminserie '%{title}' wurde von %{actor} beendet." @@ -542,9 +542,9 @@ de: label_agenda_outcome_actions: "Aktionen für die Ergebnisse von Agendapunkten" label_agenda_outcome_edit: "Ergebnis bearbeiten" label_agenda_outcome_delete: "Ergebnis entfernen" - label_added_as_outcome: "Added as outcome" - label_write_outcome: "Write outcome" - label_existing_work_package: "Existing work package" + label_added_as_outcome: "Als Ergebnis hinzugefügt" + label_write_outcome: "Ergebnis ergänzen" + label_existing_work_package: "Bestehendes Arbeitspaket" text_outcome_not_editable_anymore: "Dieses Ergebnis ist nicht mehr bearbeitbar." text_outcome_cannot_be_added: "Ein Ergebnis kann nicht mehr hinzugefügt werden." label_backlog_clear: "Backlog leeren" From ec24cfcba9c683fef919e24260eda86c569e9016 Mon Sep 17 00:00:00 2001 From: Jan Sandbrink Date: Thu, 29 Jan 2026 15:49:44 +0100 Subject: [PATCH 046/293] Allow to use regular API Keys for MCP requests Some clients, such as Claude code require dynamic client registration for their regular workflow. However, they allow to fallback to static HTTP headers for authentication. This approach allows to construct the corresponding header for Basic authentication and add it to Claude. --- config/initializers/warden.rb | 2 +- spec/requests/api/v3/support/mcp_examples.rb | 6 ++++++ spec/requests/mcp/tools_list_spec.rb | 16 +++++++++++++++- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/config/initializers/warden.rb b/config/initializers/warden.rb index adba81c4ed8..0918ad64492 100644 --- a/config/initializers/warden.rb +++ b/config/initializers/warden.rb @@ -59,7 +59,7 @@ OpenProject::Authentication.update_strategies(OpenProject::Authentication::Scope end OpenProject::Authentication.update_strategies(OpenProject::Authentication::Scope::MCP_SCOPE, { store: false }) do |_| - %i[oauth jwt_oidc] + %i[oauth jwt_oidc user_basic_auth basic_auth_failure] end Rails.application.configure do |app| diff --git a/spec/requests/api/v3/support/mcp_examples.rb b/spec/requests/api/v3/support/mcp_examples.rb index 53930887361..b1e0f0a80e1 100644 --- a/spec/requests/api/v3/support/mcp_examples.rb +++ b/spec/requests/api/v3/support/mcp_examples.rb @@ -174,4 +174,10 @@ RSpec.shared_examples_for "MCP unauthenticated response" do subject expect(last_response.headers["WWW-Authenticate"]).to be_present end + + it "indicates resource_metadata in the WWW-Authenticate header" do + subject + keys = last_response.headers["WWW-Authenticate"].split.select { |s| s.include?("=") }.map { |s| s.split("=").first } + expect(keys).to include("resource_metadata") + end end diff --git a/spec/requests/mcp/tools_list_spec.rb b/spec/requests/mcp/tools_list_spec.rb index f08fd361ffb..3440f922f8d 100644 --- a/spec/requests/mcp/tools_list_spec.rb +++ b/spec/requests/mcp/tools_list_spec.rb @@ -69,8 +69,10 @@ RSpec.describe "MCP tools/list", with_flag: { mcp_server: true } do expect(tool.fetch("description")).to eq(tool_config.description) end - context "when not passing a Bearer token" do + context "when not passing a token" do subject do + # TODO: It's actually a hack that we expect clients to provide this header for proper WWW-Authenticate responses + # Regular clients will never see the extended WWW-Authenticate headers with resource_metadata hints header "X-Authentication-Scheme", "Bearer" header "Content-Type", "application/json" post "/mcp", request_body.to_json @@ -79,6 +81,18 @@ RSpec.describe "MCP tools/list", with_flag: { mcp_server: true } do it_behaves_like "MCP unauthenticated response" end + context "when passing an API key via Basic auth" do + subject do + header "Authorization", "Basic #{Base64.encode64("apikey:#{apikey.plain_value}")}" + header "Content-Type", "application/json" + post "/mcp", request_body.to_json + end + + let(:apikey) { create(:api_token) } + + it_behaves_like "MCP result response" + end + context "when passing a Bearer token with a wrong scope" do let(:access_token) { create(:oauth_access_token, scopes: "api_v3") } From 134488374f3f83aa95879b10b5820d5ba4735dd7 Mon Sep 17 00:00:00 2001 From: OpenProject Actions CI Date: Tue, 3 Feb 2026 09:25:31 +0000 Subject: [PATCH 047/293] update locales from crowdin [ci skip] --- config/locales/crowdin/af.yml | 18 ++++++++---------- config/locales/crowdin/ar.yml | 18 ++++++++---------- config/locales/crowdin/az.yml | 18 ++++++++---------- config/locales/crowdin/be.yml | 18 ++++++++---------- config/locales/crowdin/bg.yml | 18 ++++++++---------- config/locales/crowdin/ca.yml | 18 ++++++++---------- config/locales/crowdin/ckb-IR.yml | 18 ++++++++---------- config/locales/crowdin/cs.yml | 18 ++++++++---------- config/locales/crowdin/da.yml | 18 ++++++++---------- config/locales/crowdin/de.yml | 20 +++++++++----------- config/locales/crowdin/el.yml | 18 ++++++++---------- config/locales/crowdin/eo.yml | 18 ++++++++---------- config/locales/crowdin/es.yml | 20 +++++++++----------- config/locales/crowdin/et.yml | 18 ++++++++---------- config/locales/crowdin/eu.yml | 18 ++++++++---------- config/locales/crowdin/fa.yml | 18 ++++++++---------- config/locales/crowdin/fi.yml | 18 ++++++++---------- config/locales/crowdin/fil.yml | 18 ++++++++---------- config/locales/crowdin/fr.yml | 20 +++++++++----------- config/locales/crowdin/he.yml | 18 ++++++++---------- config/locales/crowdin/hi.yml | 18 ++++++++---------- config/locales/crowdin/hr.yml | 18 ++++++++---------- config/locales/crowdin/hu.yml | 18 ++++++++---------- config/locales/crowdin/id.yml | 18 ++++++++---------- config/locales/crowdin/it.yml | 20 +++++++++----------- config/locales/crowdin/ja.yml | 18 ++++++++---------- config/locales/crowdin/ka.yml | 18 ++++++++---------- config/locales/crowdin/kk.yml | 18 ++++++++---------- config/locales/crowdin/ko.yml | 20 +++++++++----------- config/locales/crowdin/lt.yml | 18 ++++++++---------- config/locales/crowdin/lv.yml | 18 ++++++++---------- config/locales/crowdin/mn.yml | 18 ++++++++---------- config/locales/crowdin/ms.yml | 18 ++++++++---------- config/locales/crowdin/ne.yml | 18 ++++++++---------- config/locales/crowdin/nl.yml | 18 ++++++++---------- config/locales/crowdin/no.yml | 18 ++++++++---------- config/locales/crowdin/pl.yml | 20 +++++++++----------- config/locales/crowdin/pt-BR.yml | 20 +++++++++----------- config/locales/crowdin/pt-PT.yml | 20 +++++++++----------- config/locales/crowdin/ro.yml | 18 ++++++++---------- config/locales/crowdin/ru.yml | 20 +++++++++----------- config/locales/crowdin/rw.yml | 18 ++++++++---------- config/locales/crowdin/si.yml | 18 ++++++++---------- config/locales/crowdin/sk.yml | 18 ++++++++---------- config/locales/crowdin/sl.yml | 18 ++++++++---------- config/locales/crowdin/sr.yml | 18 ++++++++---------- config/locales/crowdin/sv.yml | 18 ++++++++---------- config/locales/crowdin/th.yml | 18 ++++++++---------- config/locales/crowdin/tr.yml | 18 ++++++++---------- config/locales/crowdin/uk.yml | 20 +++++++++----------- config/locales/crowdin/uz.yml | 18 ++++++++---------- config/locales/crowdin/vi.yml | 18 ++++++++---------- config/locales/crowdin/zh-CN.yml | 20 +++++++++----------- config/locales/crowdin/zh-TW.yml | 20 +++++++++----------- 54 files changed, 444 insertions(+), 552 deletions(-) diff --git a/config/locales/crowdin/af.yml b/config/locales/crowdin/af.yml index 5e117b342e0..5be36d07c55 100644 --- a/config/locales/crowdin/af.yml +++ b/config/locales/crowdin/af.yml @@ -2853,19 +2853,17 @@ af: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Upgrade to Enterprise edition" postgres_migration: "Migrating your installation to PostgreSQL" diff --git a/config/locales/crowdin/ar.yml b/config/locales/crowdin/ar.yml index 27a95eba4fe..0391dfd6bc8 100644 --- a/config/locales/crowdin/ar.yml +++ b/config/locales/crowdin/ar.yml @@ -3049,19 +3049,17 @@ ar: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Upgrade to Enterprise edition" postgres_migration: "Migrating your installation to PostgreSQL" diff --git a/config/locales/crowdin/az.yml b/config/locales/crowdin/az.yml index 2a8f6599c42..0af567a73e6 100644 --- a/config/locales/crowdin/az.yml +++ b/config/locales/crowdin/az.yml @@ -2853,19 +2853,17 @@ az: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Upgrade to Enterprise edition" postgres_migration: "Migrating your installation to PostgreSQL" diff --git a/config/locales/crowdin/be.yml b/config/locales/crowdin/be.yml index 2b17c25df8c..437f2657858 100644 --- a/config/locales/crowdin/be.yml +++ b/config/locales/crowdin/be.yml @@ -2951,19 +2951,17 @@ be: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Upgrade to Enterprise edition" postgres_migration: "Migrating your installation to PostgreSQL" diff --git a/config/locales/crowdin/bg.yml b/config/locales/crowdin/bg.yml index 1133f6b7ee1..c7e587f918b 100644 --- a/config/locales/crowdin/bg.yml +++ b/config/locales/crowdin/bg.yml @@ -2853,19 +2853,17 @@ bg: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Upgrade to Enterprise edition" postgres_migration: "Migrating your installation to PostgreSQL" diff --git a/config/locales/crowdin/ca.yml b/config/locales/crowdin/ca.yml index 75e022b7a60..8c75df019de 100644 --- a/config/locales/crowdin/ca.yml +++ b/config/locales/crowdin/ca.yml @@ -2850,19 +2850,17 @@ ca: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Actualitza a l'edició Enterprise" postgres_migration: "Migrant la teva instal·lació a PostgreSQL" diff --git a/config/locales/crowdin/ckb-IR.yml b/config/locales/crowdin/ckb-IR.yml index e08e982825b..e6b505cd173 100644 --- a/config/locales/crowdin/ckb-IR.yml +++ b/config/locales/crowdin/ckb-IR.yml @@ -2853,19 +2853,17 @@ ckb-IR: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Upgrade to Enterprise edition" postgres_migration: "Migrating your installation to PostgreSQL" diff --git a/config/locales/crowdin/cs.yml b/config/locales/crowdin/cs.yml index 487c9c0e41b..bcc47bb8401 100644 --- a/config/locales/crowdin/cs.yml +++ b/config/locales/crowdin/cs.yml @@ -2951,19 +2951,17 @@ cs: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Upgradovat na Enterprise Edici" postgres_migration: "Migrujte vaši instalaci na PostgreSQL" diff --git a/config/locales/crowdin/da.yml b/config/locales/crowdin/da.yml index 30dd2e39dc6..07a985dd2a1 100644 --- a/config/locales/crowdin/da.yml +++ b/config/locales/crowdin/da.yml @@ -2851,19 +2851,17 @@ da: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Upgrade to Enterprise edition" postgres_migration: "Migrating your installation to PostgreSQL" diff --git a/config/locales/crowdin/de.yml b/config/locales/crowdin/de.yml index 5d15452a5a3..bcac09e548d 100644 --- a/config/locales/crowdin/de.yml +++ b/config/locales/crowdin/de.yml @@ -2845,19 +2845,17 @@ de: learn_about: "Erfahren Sie mehr über die neuen Funktionen" missing: "Es gibt noch keine hervorgehobenen Funktionen." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > - Dieses Release enthält verschiedene neue Funktionen und Verbesserungen, wie z. B: + The release contains various new features and improvements, such as: new_features_list: - line_0: Zusammenarbeit an Dokumenten in Echtzeit. - line_1: Strukturierung zusammenhängender Projekte in Programme und Portfolios, um sie auf strategische Ziele auszurichten (Enterprise Add-on). - line_2: Bessere Verwaltung von Besprechungen mit Entwurfsmodus, Präsentationsmodus, verbesserten Ergebnissen und iCal-Abonnement. - line_3: Aktualisierte SharePoint-Integration mit flexibleren Berechtigungen (Enterprise Add-on). - line_4: Neu gestaltete Projektübersicht mit neuen Tabs, konfigurierbaren Widgets und einem verbesserten Layout. - line_5: Option zum Trennen der Sichtbarkeit von Benutzern innerhalb der Instanz, die nicht am selben Projekt oder derselben Gruppe arbeiten. - line_6: Verbesserter Ablauf der Projekterstellung mit neuer Vorlagenauswahl. - line_7: Eine intelligentere Suche, die Typ und Status einschließt, verbessert die Präzision in der globalen Suche und in Auswahlfeldern. - line_8: Verbesserungen der Zugänglichkeit mit ALT-Texten und verbesserten Diagrammfarben. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Auf Enterprise Edition upgraden" postgres_migration: "Migration Ihrer Installation zu PostgreSQL" diff --git a/config/locales/crowdin/el.yml b/config/locales/crowdin/el.yml index e0c03618c10..de727d8b625 100644 --- a/config/locales/crowdin/el.yml +++ b/config/locales/crowdin/el.yml @@ -2849,19 +2849,17 @@ el: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Upgrade to Enterprise edition" postgres_migration: "Μεταφορά της εγκατάστασης σας σε PostgreSQL" diff --git a/config/locales/crowdin/eo.yml b/config/locales/crowdin/eo.yml index 31f4a84781e..4eeb0e45ec6 100644 --- a/config/locales/crowdin/eo.yml +++ b/config/locales/crowdin/eo.yml @@ -2853,19 +2853,17 @@ eo: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Upgrade to Enterprise edition" postgres_migration: "Transmetanta vian instalon al PostgreSQL" diff --git a/config/locales/crowdin/es.yml b/config/locales/crowdin/es.yml index 3f514495a7a..bdc4738a1d9 100644 --- a/config/locales/crowdin/es.yml +++ b/config/locales/crowdin/es.yml @@ -2850,19 +2850,17 @@ es: learn_about: "Más información sobre todas las nuevas funciones" missing: "Aún no hay funciones destacadas." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > - El lanzamiento incluye varias funciones nuevas y mejoras, tales como: + The release contains various new features and improvements, such as: new_features_list: - line_0: Colaboración de documentos en tiempo real. - line_1: Capacidad para estructurar proyectos relacionados en programas y carteras con el fin de centrarte en objetivos estratégicos (extensión Enterprise). - line_2: Mejor gestión de reuniones con modo Borrador, modo Presentación, resultados mejorados y suscripción a iCal. - line_3: Integración actualizada de SharePoint con permisos más restrictivos (extensión Enterprise). - line_4: Vista general del proyecto rediseñada con nuevas pestañas, widgets configurables y un diseño mejorado. - line_5: Opción para proteger más estrictamente la privacidad de los usuarios que no trabajan en el mismo proyecto. - line_6: Flujo de creación de proyectos mejorado con una mejor selección de plantillas. - line_7: Búsqueda global más inteligente, incluyendo el tipo y estado, mejorando la precisión en varios autocompletadores. - line_8: Mejoras de accesibilidad con textos ALT y colores de gráficos mejorados. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Actualizar a Enterprise" postgres_migration: "Migrando su instalación a PostgreSQL" diff --git a/config/locales/crowdin/et.yml b/config/locales/crowdin/et.yml index 0ef1218c947..c6506951c29 100644 --- a/config/locales/crowdin/et.yml +++ b/config/locales/crowdin/et.yml @@ -2853,19 +2853,17 @@ et: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Upgrade to Enterprise edition" postgres_migration: "Migrating your installation to PostgreSQL" diff --git a/config/locales/crowdin/eu.yml b/config/locales/crowdin/eu.yml index 89541545fde..8eb7438058b 100644 --- a/config/locales/crowdin/eu.yml +++ b/config/locales/crowdin/eu.yml @@ -2853,19 +2853,17 @@ eu: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Upgrade to Enterprise edition" postgres_migration: "Migrating your installation to PostgreSQL" diff --git a/config/locales/crowdin/fa.yml b/config/locales/crowdin/fa.yml index 0b5eacd6270..216826d757d 100644 --- a/config/locales/crowdin/fa.yml +++ b/config/locales/crowdin/fa.yml @@ -2853,19 +2853,17 @@ fa: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Upgrade to Enterprise edition" postgres_migration: "Migrating your installation to PostgreSQL" diff --git a/config/locales/crowdin/fi.yml b/config/locales/crowdin/fi.yml index af437ce8f03..e1cfd664ded 100644 --- a/config/locales/crowdin/fi.yml +++ b/config/locales/crowdin/fi.yml @@ -2853,19 +2853,17 @@ fi: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Upgrade to Enterprise edition" postgres_migration: "Migrating your installation to PostgreSQL" diff --git a/config/locales/crowdin/fil.yml b/config/locales/crowdin/fil.yml index c8f69fa9dc8..cbe0524f12a 100644 --- a/config/locales/crowdin/fil.yml +++ b/config/locales/crowdin/fil.yml @@ -2853,19 +2853,17 @@ fil: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Upgrade to Enterprise edition" postgres_migration: "Migrating your installation to PostgreSQL" diff --git a/config/locales/crowdin/fr.yml b/config/locales/crowdin/fr.yml index 1c6e75740ab..a3c4b8ba61d 100644 --- a/config/locales/crowdin/fr.yml +++ b/config/locales/crowdin/fr.yml @@ -2851,19 +2851,17 @@ fr: learn_about: "En savoir plus sur les nouvelles fonctionnalités" missing: "Il n'y a pas encore de fonctionnalités mises en évidence." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > - Cette version contient plusieurs nouvelles fonctionnalités et améliorations, telles que : + The release contains various new features and improvements, such as: new_features_list: - line_0: Collaboration en temps réel sur les documents. - line_1: Possibilité de structurer des projets connexes en programmes et en portefeuilles afin de les axer sur des objectifs stratégiques (add-on Enterprise). - line_2: Meilleure gestion des réunions grâce au mode brouillon, au mode présentation, aux résultats améliorés et à l'abonnement iCal. - line_3: Mise à jour de l'intégration de SharePoint avec des permissions plus restrictives (add-on Enterprise). - line_4: Refonte de la vue d'ensemble du projet avec de nouveaux onglets, des widgets configurables et une mise en page améliorée. - line_5: Possibilité de protéger plus strictement la vie privée des utilisateurs qui ne travaillent pas sur le même projet. - line_6: Amélioration du flux de création de projets avec une meilleure sélection des modèles. - line_7: Recherche globale plus intelligente incluant le type et le statut afin d'améliorer la précision dans plusieurs outils de saisie automatique. - line_8: Amélioration de l'accessibilité des textes ALT et des couleurs des graphiques. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Passer à la version Enterprise" postgres_migration: "Migration de votre installation vers PostgreSQL" diff --git a/config/locales/crowdin/he.yml b/config/locales/crowdin/he.yml index 7a75ede3549..07eabe7145e 100644 --- a/config/locales/crowdin/he.yml +++ b/config/locales/crowdin/he.yml @@ -2951,19 +2951,17 @@ he: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Upgrade to Enterprise edition" postgres_migration: "Migrating your installation to PostgreSQL" diff --git a/config/locales/crowdin/hi.yml b/config/locales/crowdin/hi.yml index a3a3f39dcf2..dae636ec74f 100644 --- a/config/locales/crowdin/hi.yml +++ b/config/locales/crowdin/hi.yml @@ -2851,19 +2851,17 @@ hi: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Upgrade to Enterprise edition" postgres_migration: "Migrating your installation to PostgreSQL" diff --git a/config/locales/crowdin/hr.yml b/config/locales/crowdin/hr.yml index 9808370ee89..494ace32b13 100644 --- a/config/locales/crowdin/hr.yml +++ b/config/locales/crowdin/hr.yml @@ -2902,19 +2902,17 @@ hr: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Upgrade to Enterprise edition" postgres_migration: "Migrating your installation to PostgreSQL" diff --git a/config/locales/crowdin/hu.yml b/config/locales/crowdin/hu.yml index 7e314e67b79..42af57c3aed 100644 --- a/config/locales/crowdin/hu.yml +++ b/config/locales/crowdin/hu.yml @@ -2852,19 +2852,17 @@ hu: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Váltás Vállalati Verzióra" postgres_migration: "A rendszer költöztetése PostreSQL-re" diff --git a/config/locales/crowdin/id.yml b/config/locales/crowdin/id.yml index 2fb2a29bb7d..0a1cc35d9fe 100644 --- a/config/locales/crowdin/id.yml +++ b/config/locales/crowdin/id.yml @@ -2800,19 +2800,17 @@ id: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Upgrade to Enterprise edition" postgres_migration: "Migrasi instalasi anda ke PostgreSQL" diff --git a/config/locales/crowdin/it.yml b/config/locales/crowdin/it.yml index 90f5ec3f0c7..22314698755 100644 --- a/config/locales/crowdin/it.yml +++ b/config/locales/crowdin/it.yml @@ -2850,19 +2850,17 @@ it: learn_about: "Scopri di più su tutte le nuove funzionalità" missing: "Non ci sono ancora caratteristiche evidenziate." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > - Questa versione contiene diverse nuove funzionalità e miglioramenti, come ad esempio: + The release contains various new features and improvements, such as: new_features_list: - line_0: Collaborazione in tempo reale sui documenti. - line_1: Capacità di strutturare i progetti correlati in programmi e portfolio per focalizzarli su obiettivi strategici (componente aggiuntivo Enterprise). - line_2: Migliore gestione delle riunioni con modalità bozza, modalità di presentazione, risultati migliorati e iscrizione iCal. - line_3: Integrazione SharePoint aggiornata con autorizzazioni più restrittive (componente aggiuntivo Enterprise). - line_4: Panoramica del progetto ridisegnata con nuove schede, widget configurabili e un layout migliorato. - line_5: Opzione per tutelare in modo più rigoroso la privacy degli utenti che non lavorano nello stesso progetto. - line_6: Flusso di creazione dei progetti migliorato con una selezione dei modelli più efficace. - line_7: Ricerca globale più intelligente che include tipo e stato, migliorando la precisione in diversi completamenti automatici. - line_8: Miglioramenti di accessibilità con testi ALT e colori grafici migliorati. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Aggiorna ad Enterprise edition" postgres_migration: "Migrazione dell'installazione su PostgreSQL" diff --git a/config/locales/crowdin/ja.yml b/config/locales/crowdin/ja.yml index 3b8077b1dff..56cde2eeebd 100644 --- a/config/locales/crowdin/ja.yml +++ b/config/locales/crowdin/ja.yml @@ -2803,19 +2803,17 @@ ja: learn_about: "すべての新機能の詳細" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "エンタープライズ版にアップグレード" postgres_migration: "PostgreSQL にインストールを移行しています" diff --git a/config/locales/crowdin/ka.yml b/config/locales/crowdin/ka.yml index 019bda2bfbb..828b129b6df 100644 --- a/config/locales/crowdin/ka.yml +++ b/config/locales/crowdin/ka.yml @@ -2853,19 +2853,17 @@ ka: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Upgrade to Enterprise edition" postgres_migration: "Migrating your installation to PostgreSQL" diff --git a/config/locales/crowdin/kk.yml b/config/locales/crowdin/kk.yml index b6c45e70943..61b83f3550d 100644 --- a/config/locales/crowdin/kk.yml +++ b/config/locales/crowdin/kk.yml @@ -2853,19 +2853,17 @@ kk: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Upgrade to Enterprise edition" postgres_migration: "Migrating your installation to PostgreSQL" diff --git a/config/locales/crowdin/ko.yml b/config/locales/crowdin/ko.yml index 7fa56d31882..01d815b68e6 100644 --- a/config/locales/crowdin/ko.yml +++ b/config/locales/crowdin/ko.yml @@ -2804,19 +2804,17 @@ ko: learn_about: "새로운 모든 기능에 대해 자세히 알아보기" missing: "아직 강조 표시된 기능이 없습니다." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > - 이 릴리스에는 다음과 같은 다양한 새로운 기능과 개선 사항이 포함되어 있습니다. + The release contains various new features and improvements, such as: new_features_list: - line_0: 실시간 문서 공동 작업. - line_1: 관련 프로젝트를 프로그램 및 포트폴리오로 구성하여 전략적 목표에 집중할 수 있는 기능(Enterprise 추가 기능)입니다. - line_2: 초안 모드, 프레젠테이션 모드, 향상된 결과, iCal 구독으로 개선된 미팅 관리가 가능합니다. - line_3: 보다 제한적인 권한으로 SharePoint 통합이 업데이트되었습니다(Enterprise 추가 기능). - line_4: 새로운 탭, 구성 가능한 위젯, 개선된 레이아웃으로 프로젝트 개요를 새롭게 디자인했습니다. - line_5: 동일한 프로젝트에서 작업 중이지 않은 사용자의 개인 정보를 보다 엄격하게 보호하는 옵션입니다. - line_6: 향상된 템플릿 선택으로 프로젝트 생성 흐름이 개선되었습니다. - line_7: 유형과 상태를 포함한 더욱 스마트한 글로벌 검색으로 여러 자동 완성기의 정확도가 개선되었습니다. - line_8: ALT 텍스트와 개선된 차트 색상으로 접근성이 개선되었습니다. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Enterprise Edition으로 업그레이드" postgres_migration: "설치를 PostgreSQL로 마이그레이션" diff --git a/config/locales/crowdin/lt.yml b/config/locales/crowdin/lt.yml index 47067d677e7..bce6d8a1ef3 100644 --- a/config/locales/crowdin/lt.yml +++ b/config/locales/crowdin/lt.yml @@ -2948,19 +2948,17 @@ lt: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Pagerinti į Enterprise versiją" postgres_migration: "Migruojame jūsų instaliaciją į PostgreSQL" diff --git a/config/locales/crowdin/lv.yml b/config/locales/crowdin/lv.yml index 8ab2aa15e5a..ae6b1b8eb81 100644 --- a/config/locales/crowdin/lv.yml +++ b/config/locales/crowdin/lv.yml @@ -2902,19 +2902,17 @@ lv: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Upgrade to Enterprise edition" postgres_migration: "Migrating your installation to PostgreSQL" diff --git a/config/locales/crowdin/mn.yml b/config/locales/crowdin/mn.yml index 69304a667d1..a6c5ef127a0 100644 --- a/config/locales/crowdin/mn.yml +++ b/config/locales/crowdin/mn.yml @@ -2853,19 +2853,17 @@ mn: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Upgrade to Enterprise edition" postgres_migration: "Migrating your installation to PostgreSQL" diff --git a/config/locales/crowdin/ms.yml b/config/locales/crowdin/ms.yml index 35956d36bb7..14bc7e1e267 100644 --- a/config/locales/crowdin/ms.yml +++ b/config/locales/crowdin/ms.yml @@ -2802,19 +2802,17 @@ ms: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Naik taraf ke edisi Enterprise" postgres_migration: "Mengalihkan pemasangan anda ke PostgreSQL" diff --git a/config/locales/crowdin/ne.yml b/config/locales/crowdin/ne.yml index 1741420ab4c..8ebbc14c2ac 100644 --- a/config/locales/crowdin/ne.yml +++ b/config/locales/crowdin/ne.yml @@ -2853,19 +2853,17 @@ ne: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Upgrade to Enterprise edition" postgres_migration: "Migrating your installation to PostgreSQL" diff --git a/config/locales/crowdin/nl.yml b/config/locales/crowdin/nl.yml index f00131a1be7..52d6ad57859 100644 --- a/config/locales/crowdin/nl.yml +++ b/config/locales/crowdin/nl.yml @@ -2849,19 +2849,17 @@ nl: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Upgrade naar Enterprise-editie" postgres_migration: "Uw installatie overzetten naar PostgreSQL" diff --git a/config/locales/crowdin/no.yml b/config/locales/crowdin/no.yml index 5e88aef507f..8a8fece1021 100644 --- a/config/locales/crowdin/no.yml +++ b/config/locales/crowdin/no.yml @@ -2852,19 +2852,17 @@ learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Oppgrader til Enterprise utgaven" postgres_migration: "Overføre installasjonen til PostgreSQL" diff --git a/config/locales/crowdin/pl.yml b/config/locales/crowdin/pl.yml index 100b2670997..85a9e7ca3f3 100644 --- a/config/locales/crowdin/pl.yml +++ b/config/locales/crowdin/pl.yml @@ -2947,19 +2947,17 @@ pl: learn_about: "Dowiedz się więcej o wszystkich nowych funkcjach" missing: "Nie ma jeszcze wyróżnionych funkcji." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > - Wydanie zawiera różne nowe funkcje i ulepszenia, takie jak: + The release contains various new features and improvements, such as: new_features_list: - line_0: Współpraca nad dokumentami w czasie rzeczywistym. - line_1: Zdolność do strukturyzowania powiązanych projektów w programy i portfolia, aby skoncentrować je na celach strategicznych (dodatek wersji Enterprise). - line_2: Lepsze zarządzanie spotkaniami dzięki trybowi wersji roboczej, trybowi prezentacji, ulepszonym wynikom i subskrypcji iCal. - line_3: Zaktualizowana integracja usługi SharePoint z bardziej restrykcyjnymi uprawnieniami (dodatek wersji Enterprise). - line_4: Przeprojektowany przegląd projektu z nowymi kartami, konfigurowanymi widżetami i ulepszonym układem. - line_5: Opcja bardziej rygorystycznej ochrony prywatności użytkowników nie pracujących w tym samym projekcie. - line_6: Ulepszony przepływ tworzenia projektów z lepszym wyborem szablonów. - line_7: Inteligentniejsze wyszukiwanie globalne obejmujące typ i status, poprawiające precyzję w kilku autouzupełnieniach. - line_8: Ulepszenia ułatwień dostępu dzięki tekstom ALT i ulepszonym kolorom wykresów. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Aktualizuj do wersji Enterprise" postgres_migration: "Migrowanie instalacji do PostgreSQL" diff --git a/config/locales/crowdin/pt-BR.yml b/config/locales/crowdin/pt-BR.yml index babe03a87a0..eece4d76aff 100644 --- a/config/locales/crowdin/pt-BR.yml +++ b/config/locales/crowdin/pt-BR.yml @@ -2850,19 +2850,17 @@ pt-BR: learn_about: "Saiba mais sobre todos os novos recursos" missing: "Ainda não há recursos destacados." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > - A versão contém diversos novos recursos e melhorias, como: + The release contains various new features and improvements, such as: new_features_list: - line_0: Colaboração em documentos em tempo real. - line_1: Capacidade de organizar projetos relacionados em programas e portfólios para alinhá-los aos objetivos estratégicos (complemento Enterprise). - line_2: Melhor gestão de reuniões com modo rascunho, modo de apresentação, resultados aprimorados e assinatura iCal. - line_3: Integração com SharePoint atualizada com permissões mais restritivas (complemento Enterprise). - line_4: Visão geral do projeto reformulada, com novas abas, widgets configuráveis e layout aprimorado. - line_5: Opção para proteger melhor a privacidade de usuários que não participam do mesmo projeto. - line_6: Fluxo de criação de projetos aprimorado, com seleção de modelos mais eficiente. - line_7: Busca global mais inteligente, incluindo tipo e status, aumentando a precisão em diversos autocompletadores. - line_8: Melhorias de acessibilidade com textos ALT e cores de gráfico aprimoradas. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Atualizar para a edição Enterprise" postgres_migration: "Migrando sua instalação para PostgreSQL" diff --git a/config/locales/crowdin/pt-PT.yml b/config/locales/crowdin/pt-PT.yml index 83e03351050..6a8c61a02fa 100644 --- a/config/locales/crowdin/pt-PT.yml +++ b/config/locales/crowdin/pt-PT.yml @@ -2850,19 +2850,17 @@ pt-PT: learn_about: "Mais informações sobre todas as novas funcionalidades" missing: "Ainda não há nenhum recurso destacado." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > - A versão contém várias novas funcionalidades e melhorias, tais como: + The release contains various new features and improvements, such as: new_features_list: - line_0: Colaboração em documentos em tempo real. - line_1: Capacidade de estruturar projetos relacionados em programas e carteiras, para os concentrar em objetivos estratégicos (complemento Enterprise). - line_2: Melhor gestão de reuniões com modo de rascunho, modo de apresentação, resultados melhorados e subscrição iCal. - line_3: Integração atualizada do SharePoint com permissões mais restritivas (complemento Enterprise). - line_4: Síntese do projeto redesenhada com novos separadores, widgets configuráveis e um esquema melhorado. - line_5: Opção para proteger mais rigorosamente a privacidade dos utilizadores que não trabalham no mesmo projeto. - line_6: Melhoria do fluxo de criação de projetos com uma melhor seleção de modelos. - line_7: Pesquisa global mais inteligente, com filtros por tipo e estado, aumentando a precisão em múltiplas sugestões automáticas. - line_8: Melhorias de acessibilidade com textos ALT e cores de gráficos melhoradas. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Aprimorar para a edição Enterprise" postgres_migration: "A migrar a sua instalação para PostgreSQL" diff --git a/config/locales/crowdin/ro.yml b/config/locales/crowdin/ro.yml index a3806200bd4..156f573ef40 100644 --- a/config/locales/crowdin/ro.yml +++ b/config/locales/crowdin/ro.yml @@ -2902,19 +2902,17 @@ ro: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Actualizează la ediția Enterprise" postgres_migration: "Migrarea instalației dvs. către PostgreSQL" diff --git a/config/locales/crowdin/ru.yml b/config/locales/crowdin/ru.yml index ef31113d6ca..9b1ec6c37f3 100644 --- a/config/locales/crowdin/ru.yml +++ b/config/locales/crowdin/ru.yml @@ -2949,19 +2949,17 @@ ru: learn_about: "Узнайте больше о всех новых функциях" missing: "Нет выделенных функций." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > - Релиз содержит различные новые функции и улучшения, такие как: + The release contains various new features and improvements, such as: new_features_list: - line_0: Совместная работа с документами в режиме реального времени. - line_1: Возможность структурировать связанные проекты в программы и портфели, чтобы направить их на достижение стратегических целей. - line_2: Улучшенное управление совещаниями с режимом черновика, режимом презентации, улучшенными результатами и подпиской на iCal. - line_3: Обновленная интеграция с SharePoint с более строгими разрешениями. - line_4: Переработанный обзор проекта с новыми вкладками, настраиваемыми виджетами и улучшенным макетом. - line_5: Возможность более строго охранять конфиденциальность пользователей, не работающих в одном проекте. - line_6: Улучшенный процесс создания проекта с более удобным выбором шаблона. - line_7: Более удобный глобальный поиск, включая тип и статус, улучшение точности нескольких автозаполнений. - line_8: Улучшена доступность текстов ALT и улучшены цвета графиков. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Обновить до корпоративной версии" postgres_migration: "Перенос вашей установки в PostgreSQL" diff --git a/config/locales/crowdin/rw.yml b/config/locales/crowdin/rw.yml index 30367e16eb1..d49f677e654 100644 --- a/config/locales/crowdin/rw.yml +++ b/config/locales/crowdin/rw.yml @@ -2853,19 +2853,17 @@ rw: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Upgrade to Enterprise edition" postgres_migration: "Migrating your installation to PostgreSQL" diff --git a/config/locales/crowdin/si.yml b/config/locales/crowdin/si.yml index b45fd474d9d..24a2b22c248 100644 --- a/config/locales/crowdin/si.yml +++ b/config/locales/crowdin/si.yml @@ -2853,19 +2853,17 @@ si: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Upgrade to Enterprise edition" postgres_migration: "ඔබගේ ස්ථාපනය PostgreSQL වෙත සංක්රමණය කිරීම" diff --git a/config/locales/crowdin/sk.yml b/config/locales/crowdin/sk.yml index d21ee859964..3852a5c503d 100644 --- a/config/locales/crowdin/sk.yml +++ b/config/locales/crowdin/sk.yml @@ -2951,19 +2951,17 @@ sk: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Upgrade to Enterprise edition" postgres_migration: "Migrating your installation to PostgreSQL" diff --git a/config/locales/crowdin/sl.yml b/config/locales/crowdin/sl.yml index b1e55c3911b..827facb7581 100644 --- a/config/locales/crowdin/sl.yml +++ b/config/locales/crowdin/sl.yml @@ -2950,19 +2950,17 @@ sl: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Upgrade to Enterprise edition" postgres_migration: "Selitev namestitve na PostgreSQL" diff --git a/config/locales/crowdin/sr.yml b/config/locales/crowdin/sr.yml index 6127f325a5d..85ffbe56c89 100644 --- a/config/locales/crowdin/sr.yml +++ b/config/locales/crowdin/sr.yml @@ -2902,19 +2902,17 @@ sr: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Upgrade to Enterprise edition" postgres_migration: "Migrating your installation to PostgreSQL" diff --git a/config/locales/crowdin/sv.yml b/config/locales/crowdin/sv.yml index 14cd2376405..63551ec37ae 100644 --- a/config/locales/crowdin/sv.yml +++ b/config/locales/crowdin/sv.yml @@ -2853,19 +2853,17 @@ sv: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Uppgradera till Enterprise utgåvan" postgres_migration: "Migrera din installation till PostgreSQL" diff --git a/config/locales/crowdin/th.yml b/config/locales/crowdin/th.yml index 8c9e081f865..a9788625133 100644 --- a/config/locales/crowdin/th.yml +++ b/config/locales/crowdin/th.yml @@ -2804,19 +2804,17 @@ th: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Upgrade to Enterprise edition" postgres_migration: "Migrating your installation to PostgreSQL" diff --git a/config/locales/crowdin/tr.yml b/config/locales/crowdin/tr.yml index 42e3d917cd7..8997d25a451 100644 --- a/config/locales/crowdin/tr.yml +++ b/config/locales/crowdin/tr.yml @@ -2853,19 +2853,17 @@ tr: learn_about: "Tüm yeni özellikler hakkında daha fazla bilgi edinin" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Enterprise sürümüne yükseltin" postgres_migration: "Kurulumunuzu PostgreSQL'e taşıyın" diff --git a/config/locales/crowdin/uk.yml b/config/locales/crowdin/uk.yml index 3ed0267695d..ac5d0888632 100644 --- a/config/locales/crowdin/uk.yml +++ b/config/locales/crowdin/uk.yml @@ -2945,19 +2945,17 @@ uk: learn_about: "Дізнайтеся більше про всі нові функції" missing: "Ще немає виділених функцій." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > - Випуск включає різноманітні нові функції і покращення, такі як: + The release contains various new features and improvements, such as: new_features_list: - line_0: спільна робота з документами в реальному часі; - line_1: можливість структурувати пов’язані проєкти за допомогою програм і портфелів, щоб узгодити їх зі стратегічними цілями (доповнення версії Enterprise); - line_2: зручніше керування нарадами завдяки режиму чернетки, режиму презентації, покращеним результатам і підписці iCal; - line_3: оновлена інтеграція із SharePoint із суворішими дозволами (доповнення версії Enterprise); - line_4: 'оновлений дизайн сторінки «Огляд проєкту»: нові вкладки, віджети, які можна налаштовувати, і покращений макет;' - line_5: можливість застосовувати суворіший захист конфіденційність користувачів, які не працюють в одному проєкті; - line_6: зручніший процес створення проєктів завдяки кращому вибору шаблонів; - line_7: 'зручніший глобальний пошук: ураховує тип і статус, підвищено точність автозаповнення кількох типів даних;' - line_8: 'покращення для людей з обмеженими фізичними можливостями: вдосконалено тексти ALT і кольори діаграм.' + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Оновлення до версії Enterprise" postgres_migration: "Міграція інсталяції в PostgreSQL" diff --git a/config/locales/crowdin/uz.yml b/config/locales/crowdin/uz.yml index b078b1ced32..2a2ffd35dc6 100644 --- a/config/locales/crowdin/uz.yml +++ b/config/locales/crowdin/uz.yml @@ -2853,19 +2853,17 @@ uz: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Upgrade to Enterprise edition" postgres_migration: "Migrating your installation to PostgreSQL" diff --git a/config/locales/crowdin/vi.yml b/config/locales/crowdin/vi.yml index f2364369ea2..ddcef2adf5a 100644 --- a/config/locales/crowdin/vi.yml +++ b/config/locales/crowdin/vi.yml @@ -2804,19 +2804,17 @@ vi: learn_about: "Learn more about all new features" missing: "There are no highlighted features yet." #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > The release contains various new features and improvements, such as: new_features_list: - line_0: Real-time documents collaboration. - line_1: Ability to structure related projects into programs and portfolios to focus them on strategic goals (Enterprise add-on). - line_2: Better meeting management with draft mode, presentation mode, enhanced outcomes, and iCal subscription. - line_3: Updated SharePoint integration with more restrictive permissions (Enterprise add-on). - line_4: Redesigned project overview with new tabs, configurable widgets, and an improved layout. - line_5: Option to guard the privacy of users not working in the same project more strictly. - line_6: Improved project creation flow with better template selection. - line_7: Smarter global search including type and status, improving precision in several autocompleters. - line_8: Accessibility improvements with ALT texts and improved chart colors. + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "Nâng cấp lên phiên bản Enterprise" postgres_migration: "Di chuyển cài đặt của bạn lên PostgreSQL" diff --git a/config/locales/crowdin/zh-CN.yml b/config/locales/crowdin/zh-CN.yml index 9ca0dd23ceb..0c0ef6be327 100644 --- a/config/locales/crowdin/zh-CN.yml +++ b/config/locales/crowdin/zh-CN.yml @@ -2800,19 +2800,17 @@ zh-CN: learn_about: "详细了解所有新功能" missing: "目前还没有高亮显示的功能。" #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > - 此版本包含各种新功能和改进,例如: + The release contains various new features and improvements, such as: new_features_list: - line_0: 实时文档协作。 - line_1: 能够将相关项目构建成项目群与项目组合,使其专注于战略目标(企业版附加组件)。 - line_2: 更好的会议管理,包括草稿模式、演示模式、增强的结果和 iCal 订阅。 - line_3: 更新了 SharePoint 集成,具有更严格的权限(企业版附加组件)。 - line_4: 重新设计了项目概览,具有新的选项卡、可配置的微件以及改进的布局。 - line_5: 可以选择更严格地保护不在同一项目中工作的用户的隐私。 - line_6: 改进了项目创建流程,可以更好地选择模板。 - line_7: 更智能的全局搜索,包括类型和状态,提高了多个自动补全工具的精确度。 - line_8: 通过替代文本和改进的图表颜色,提高了可访问性。 + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "升级到企业版" postgres_migration: "将您的安装迁移到 PostgreSQL" diff --git a/config/locales/crowdin/zh-TW.yml b/config/locales/crowdin/zh-TW.yml index 3fc2d565644..17e798cbb8d 100644 --- a/config/locales/crowdin/zh-TW.yml +++ b/config/locales/crowdin/zh-TW.yml @@ -2800,19 +2800,17 @@ zh-TW: learn_about: "進一步瞭解所有新功能" missing: "目前還沒有突出顯示的功能。" #We need to include the version to invalidate outdated translations in other locales - "17_0": + "17_1": new_features_title: > - 該版本包含多種新功能和改進,例如: + The release contains various new features and improvements, such as: new_features_list: - line_0: 即時文件協作。 - line_1: 有能力將相關專案架構為計畫和組合,使其專注於策略目標(企業附加元件)。 - line_2: 透過草稿模式、簡報模式、增強成果和 iCal 訂閱功能,提供更佳的會議管理。 - line_3: 更新 SharePoint 整合,提供更多限制性權限 (企業附加元件)。 - line_4: 重新設計的專案總覽,新增標籤、可設定的小工具,並改善版面設計。 - line_5: 可選擇更嚴格地保護不在同一專案中工作的使用者的隱私。 - line_6: 改善專案建立流程,提供更好的範本選擇。 - line_7: 更聰明的全局搜尋,包括類型和狀態,提高多種自動完成工具的精確度。 - line_8: 改善 ALT 文字的可讀性,並改善圖表顏色。 + line_0: Automated project initiation (Enterprise add-on). + line_1: "Meetings: add new or existing work packages as outcomes." + line_2: "Meetings: show participant responses in iCal subscriptions." + line_3: "Recurring meetings: duplicate agenda items to the next occurrence." + line_4: "Release to Community: Attribute highlighting." + line_5: Warning before opening external links in user-provided content (Enterprise add-on). + line_6: Improved performance and user experience, including the Activity tab and Documents module. links: upgrade_enterprise_edition: "升級到企業版" postgres_migration: "將您的安裝遷移到 PostgreSQL" From 4f3ea1957fb84f5df2326e3617e02da5cfe344eb Mon Sep 17 00:00:00 2001 From: OpenProject Actions CI Date: Tue, 3 Feb 2026 09:36:47 +0000 Subject: [PATCH 048/293] update locales from crowdin [ci skip] --- config/locales/crowdin/de.yml | 2 +- modules/documents/config/locales/crowdin/de.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/locales/crowdin/de.yml b/config/locales/crowdin/de.yml index bcac09e548d..ca218dcf3fa 100644 --- a/config/locales/crowdin/de.yml +++ b/config/locales/crowdin/de.yml @@ -2847,7 +2847,7 @@ de: #We need to include the version to invalidate outdated translations in other locales "17_1": new_features_title: > - The release contains various new features and improvements, such as: + Dieses Release enthält verschiedene neue Funktionen und Verbesserungen, wie z. B: new_features_list: line_0: Automated project initiation (Enterprise add-on). line_1: "Meetings: add new or existing work packages as outcomes." diff --git a/modules/documents/config/locales/crowdin/de.yml b/modules/documents/config/locales/crowdin/de.yml index 5371bb6dae0..533c0aab6fc 100644 --- a/modules/documents/config/locales/crowdin/de.yml +++ b/modules/documents/config/locales/crowdin/de.yml @@ -73,7 +73,7 @@ de: action: Noch einmal versuchen connection_recovery_notice: description: "Die Verbindung zum Server für Echtzeit-Kollaboration wurde wiederhergestellt." - tabs: "Document tabs" + tabs: "Dokument-Tabs" index_page: name: "Name" type: "Art" From 85d6dc249bb9c50cd514757c08c8ab564fc1fd17 Mon Sep 17 00:00:00 2001 From: Christophe Bliard Date: Tue, 3 Feb 2026 10:47:47 +0100 Subject: [PATCH 049/293] Add `triggered_by_url` when calling downstream workflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pass the current workflow run URL to downstream workflows so they can display a link back to the triggering workflow in their job summary. This enables traceability across the workflow chain: - continuous-delivery.yml → openproject-flavours/ci.yml - downstream-ci.yml → saas-openproject/test-saas.yml Co-Authored-By: Claude Opus 4.5 --- .github/workflows/continuous-delivery.yml | 4 +++- .github/workflows/downstream-ci.yml | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/continuous-delivery.yml b/.github/workflows/continuous-delivery.yml index f59458f4394..24d6f427c1b 100644 --- a/.github/workflows/continuous-delivery.yml +++ b/.github/workflows/continuous-delivery.yml @@ -21,8 +21,10 @@ jobs: REPOSITORY: opf/openproject-flavours WORKFLOW_ID: ci.yml REF_NAME: ${{ github.ref_name }} + THIS_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} run: | - PAYLOAD=$(jq -n --arg ref "$REF_NAME" '{"ref": "dev", "inputs": {"ref": $ref}}') + PAYLOAD=$(jq -n --arg ref "$REF_NAME" --arg triggered_by_url "$THIS_RUN_URL" \ + '{"ref": "dev", "inputs": {"ref": $ref, "triggered_by_url": $triggered_by_url}}') curl -i --fail-with-body -H"authorization: Bearer $TOKEN" \ -XPOST -H"Accept: application/vnd.github.v3+json" \ https://api.github.com/repos/$REPOSITORY/actions/workflows/$WORKFLOW_ID/dispatches \ diff --git a/.github/workflows/downstream-ci.yml b/.github/workflows/downstream-ci.yml index 1495e2fddd1..318ebe64e58 100644 --- a/.github/workflows/downstream-ci.yml +++ b/.github/workflows/downstream-ci.yml @@ -38,6 +38,7 @@ jobs: BASE_REF: ${{ github.base_ref }} HEAD_REF: ${{ github.event.pull_request.head.ref }} REF_NAME: ${{ github.ref_name }} + THIS_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} # ref: # * on push this will be `dev` or a specific release branch (e.g. release/16.1) - there is always a matching branch for that downstream # * on pull_request we use the PR branch's base (e.g. dev or a release branch) to match the above @@ -64,7 +65,8 @@ jobs: exit 0 fi - PAYLOAD=$(jq -n --arg ref "$REF" --arg core_ref "$CORE_REF" '{"ref": $ref, "inputs": {"core_ref": $core_ref}}') + PAYLOAD=$(jq -n --arg ref "$REF" --arg core_ref "$CORE_REF" --arg triggered_by_url "$THIS_RUN_URL" \ + '{"ref": $ref, "inputs": {"core_ref": $core_ref, "triggered_by_url": $triggered_by_url}}') OUTPUT_FILE=/tmp/request-output echo "Triggering $WORKFLOW_ID workflow on $REPOSITORY branch '$REF', which will check out core branch '$CORE_REF'" From f542ed6e94cb44f00c78e3060733880d606bb8d1 Mon Sep 17 00:00:00 2001 From: OpenProject Actions CI Date: Tue, 3 Feb 2026 09:59:19 +0000 Subject: [PATCH 050/293] update locales from crowdin [ci skip] --- config/locales/crowdin/de.yml | 28 ++++++++++++++-------------- config/locales/crowdin/js-de.yml | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/config/locales/crowdin/de.yml b/config/locales/crowdin/de.yml index ca218dcf3fa..47906385d2d 100644 --- a/config/locales/crowdin/de.yml +++ b/config/locales/crowdin/de.yml @@ -2849,13 +2849,13 @@ de: new_features_title: > Dieses Release enthält verschiedene neue Funktionen und Verbesserungen, wie z. B: new_features_list: - line_0: Automated project initiation (Enterprise add-on). - line_1: "Meetings: add new or existing work packages as outcomes." - line_2: "Meetings: show participant responses in iCal subscriptions." - line_3: "Recurring meetings: duplicate agenda items to the next occurrence." - line_4: "Release to Community: Attribute highlighting." - line_5: Warning before opening external links in user-provided content (Enterprise add-on). - line_6: Improved performance and user experience, including the Activity tab and Documents module. + line_0: Automatisierte Projektinitiierung (Enterprise Add-on). + line_1: "Besprechungen: Fügen Sie neue oder bestehende Arbeitspakete als Ergebnisse hinzu." + line_2: "Besprechungen: Zeigen Sie Teilnehmerantworten in iCal-Abonnements an." + line_3: "Wiederkehrende Besprechungen: Kopieren Sie Tagesordnungspunkte zur nächsten Besprechung." + line_4: "Freigabe für die Community-Edition: Hervorhebung von Attributen." + line_5: Warnung vor dem Öffnen externer Links in von Benutzern erstellten Texten (Enterprise Add-on). + line_6: Verbesserte Leistung und Benutzerfreundlichkeit, einschließlich des Aktivität-Tabs und des Dokumenten-Moduls. links: upgrade_enterprise_edition: "Auf Enterprise Edition upgraden" postgres_migration: "Migration Ihrer Installation zu PostgreSQL" @@ -2933,7 +2933,7 @@ de: label: "Hinzufügen…" my_account: access_tokens: - description: "Provider tokens are issued by OpenProject, allowing other applications to access it. Client tokens are issued by other applications, allowing OpenProject to access them." + description: "Provider-Tokens werden von OpenProject ausgestellt und ermöglichen anderen Anwendungen den Zugriff darauf. Client-Tokens werden von anderen Anwendungen ausgestellt und ermöglichen OpenProject den Zugriff auf sie." no_results: title: "Kein Zugangs-Token kann angezeigt werden" description: "Alle wurden deaktiviert. Sie können im Administrations-Menü wieder aktiviert werden." @@ -4319,16 +4319,16 @@ de: Wenn CORS aktiviert ist, sind dies die Origins, die auf die OpenProject API zugreifen dürfen.
Bitte überprüfen Sie die Dokumentation über den Origin Header, wie die Werte anzugeben sind. setting_apiv3_write_readonly_attributes: "Schreibzugriff auf schreibgeschützte Attribute" setting_apiv3_write_readonly_attributes_instructions: > - If enabled, the API will allow administrators to write static read-only attributes during creation, such as createdAt and author. + Wenn diese Option aktiviert ist, erlaubt die API Administratoren, bei der Erstellung statische schreibgeschützte Attribute wie createdAt und author zu schreiben. setting_apiv3_write_readonly_attributes_warning: > - This setting has a use-case for e.g., importing data, but allows administrators to impersonate the creation of items as other users. All creation requests are being logged however with the true author. + Diese Einstellung ist z.B. für den Import von Daten nützlich, ermöglicht es aber auch Administratoren, sich bei der Erstellung von Artikeln als andere Benutzer auszugeben. Alle Erstellungsanfragen werden jedoch mit dem wahren Autor protokolliert. setting_apiv3_write_readonly_attributes_additional: > - For more information on attributes and supported resources, please see the %{api_documentation_link}. + Weitere Informationen zu Attributen und unterstützten Ressourcen finden Sie unter %{api_documentation_link}. setting_apiv3_max_page_size: "Maximale API-Seitengröße" setting_apiv3_max_page_size_instructions: > - Set the maximum page size the API will respond with. It will not be possible to perform API requests that return more values on a single page. + Legen Sie die maximale Anzahl von Ressourcen fest, mit der die API antworten wird. API-Anfragen werden nie mehr Ressourcen auf einer einzigen Seite zurückgeben. setting_apiv3_max_page_size_warning: > - Please only change this value if you are sure why you need it. Setting to a high value will result in significant performance impacts, while a value lower than the per page options will cause errors in paginated views. + Bitte ändern Sie diesen Wert nur, wenn Sie sich sicher sind, warum Sie ihn benötigen. Ein hoher Wert führt zu erheblichen Leistungseinbußen, während ein Wert, der niedriger als die Ressourcen pro Seite ist, zu Fehlern in paginierten Ansichten führt. setting_apiv3_docs: "Dokumentation" setting_apiv3_docs_enabled: "Dokumentationsseite aktivieren" setting_apiv3_docs_enabled_instructions_html: > @@ -4513,7 +4513,7 @@ de: omniauth_direct_login_hint_html: > Wenn diese Option aktiviert ist, werden die Anmeldeanfragen an den konfigurierten SSO-Anbieter weitergeleitet. Das Login-Dropdown und die Anmeldeseite sind dann deaktiviert.
Hinweis: Sofern Sie nicht auch die Passwortanmeldung deaktivieren, können sich Benutzer bei aktivierter Option immer noch intern anmelden, indem sie die interne Anmeldeseite %{internal_path} verwenden. remapping_existing_users_hint: > - If enabled, allows any configured identity provider to login existing users based on their username, even if the user never signed in through that provider before. This can be useful when migrating the OpenProject instance to a new SSO provider, but is not recommended when using a provider that is not trusted by all users of your instance. + Wenn diese Option aktiviert ist, kann jeder konfigurierte Identitätsanbieter bestehende Benutzer anhand ihrer E-Mail-Adresse oder Login erzeugen, auch wenn sich der Benutzer noch nie über diesen Anbieter angemeldet hat. Dies kann bei der Migration der OpenProject-Instanz zu einem neuen SSO-Anbieter nützlich sein, wird aber nicht empfohlen, wenn Sie einen Anbieter verwenden, dem nicht alle Benutzer Ihrer Instanz vertrauen. attachments: whitelist_text_html: > Legen Sie eine Liste gültiger Dateierweiterungen und/oder Mime-Typen für hochgeladene Dateien fest.
Dateierweiterungen (z. B. %{ext_example}) oder Mime-Typen (z. ., %{mime_example}).
Lassen Sie diese Liste leer, um das Hochladen beliebiger Dateitypen zu erlauben. Mehrere Werte erlaubt (eine Zeile pro Wert). diff --git a/config/locales/crowdin/js-de.yml b/config/locales/crowdin/js-de.yml index e380b584676..d37cb74b060 100644 --- a/config/locales/crowdin/js-de.yml +++ b/config/locales/crowdin/js-de.yml @@ -425,7 +425,7 @@ de: label_remove_row: "Zeile entfernen" label_report: "Auswertung" label_repository_plural: "Projektarchive" - label_resize_project_menu: "Resize project menu" + label_resize_project_menu: "Größe des Projektmenüs ändern" label_save_as: "Speichern unter" label_search_columns: "Spalte suchen" label_select_watcher: "Beobachter auswählen ..." From 53273d5fdfe18e8fb18a6b99166f28ea9a9e9b2a Mon Sep 17 00:00:00 2001 From: ulferts Date: Tue, 3 Feb 2026 12:09:22 +0100 Subject: [PATCH 051/293] always generate subject unless overridden - no placeholder text set --- .../work_packages/set_attributes_service.rb | 14 ++-- config/locales/en.yml | 1 - config/locales/js-en.yml | 1 + docs/api/apiv3/tags/work_packages.yml | 51 ++++++------- .../display/display-field.initializer.ts | 4 ++ .../subject-display-field.module.ts | 41 +++++++++++ .../schema/work_package_schema_representer.rb | 5 +- .../work_package_schema_representer_spec.rb | 21 +++++- .../set_attributes_service_spec.rb | 72 ++++++++----------- 9 files changed, 132 insertions(+), 78 deletions(-) create mode 100644 frontend/src/app/shared/components/fields/display/field-types/subject-display-field.module.ts diff --git a/app/services/work_packages/set_attributes_service.rb b/app/services/work_packages/set_attributes_service.rb index 448d613c862..e2b35eec098 100644 --- a/app/services/work_packages/set_attributes_service.rb +++ b/app/services/work_packages/set_attributes_service.rb @@ -43,22 +43,22 @@ class WorkPackages::SetAttributesService < BaseServices::SetAttributes model.change_by_system do set_calculated_attributes(attributes) - set_templated_subject end set_custom_attributes(attributes) set_custom_values_to_validate(attributes, validate_custom_fields) + + # Needs to be late so that updates to values can be taken into account. + set_templated_subject end def set_templated_subject return unless work_package.type&.replacement_pattern_defined_for?(:subject) - return if work_package.subject.present? + return if work_package.subject_changed? - work_package.subject = if work_package.new_record? - I18n.t("work_packages.templated_subject_hint", type: work_package.type.name) - else - work_package.type.enabled_patterns[:subject].resolve(work_package) - end + model.change_by_system do + work_package.subject = work_package.type.enabled_patterns[:subject].resolve(work_package) + end end def set_custom_values_to_validate(attributes, validate_custom_fields = nil) diff --git a/config/locales/en.yml b/config/locales/en.yml index 3b20d2b8859..e382d9283d7 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1101,7 +1101,6 @@ en: title: "Workflow missing for work package sharing" message: "No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role." link_message: "Configure the workflows in the administration." - templated_subject_hint: Automatically generated through type %{type} summary: reports: diff --git a/config/locales/js-en.yml b/config/locales/js-en.yml index b4fa4cc290d..0c6de8ff07c 100644 --- a/config/locales/js-en.yml +++ b/config/locales/js-en.yml @@ -689,6 +689,7 @@ en: placeholders: default: "-" subject: "Enter subject here" + subject_auto_generated: "Automatically generated through type %{type}" selection: "Please select" description: "Description: Click to edit..." relation_description: "Click to add description for this relation" diff --git a/docs/api/apiv3/tags/work_packages.yml b/docs/api/apiv3/tags/work_packages.yml index 0b999fded32..57f4a9ae71c 100644 --- a/docs/api/apiv3/tags/work_packages.yml +++ b/docs/api/apiv3/tags/work_packages.yml @@ -51,31 +51,32 @@ description: |- ## Local Properties - | Property | Description | Type | Constraints | Supported operations | Condition | - | :--------------: | ------------------------------------------------------ | ----------- | ------------------------------------------------------------------------------------------------------ | -------------------- | -------------------------------- | - | id | Work package id | Integer | x > 0 | READ | | - | lockVersion | The version of the item as used for optimistic locking | Integer | | READ | | - | subject | Work package subject | String | not null; 1 <= length <= 255 | READ / WRITE | | - | type | Name of the work package's type | String | not null | READ | | - | description | The work package description | Formattable | | READ / WRITE | | - | scheduleManually | Uses manual scheduling mode when true (default). Uses automatic scheduling mode when false. Can be automatic only when predecessors or children are present. | Boolean | | READ / WRITE | | - | startDate | Scheduled beginning of a work package | Date | Cannot be set for parent work packages unless it is scheduled manually; must be equal or greater than the earliest possible start date; Exists only on work packages of a non milestone type | READ / WRITE | | - | dueDate | Scheduled end of a work package | Date | Cannot be set for parent work packages unless it is scheduled manually; must be greater than or equal to the start date; Exists only on work packages of a non milestone type | READ / WRITE | | - | date | Date on which a milestone is achieved | Date | Exists only on work packages of a milestone type | READ / WRITE | | - | derivedStartDate | Similar to start date but is not set by a client but rather deduced by the work packages' descendants. If manual scheduleManually is active, the two dates can deviate. | Date | | READ | | - | derivedDueDate | Similar to due date but is not set by a client but rather deduced by the work packages' descendants. If manual scheduleManually is active, the two dates can deviate. | Date | | READ | | - | duration | The amount of time in hours the work package needs to be completed. | Duration | Not available for milestone type of work packages. | READ / WRITE | | - | estimatedTime | Corresponds to work. Time a work package likely needs to be completed. | Duration | | READ / WRITE | | - | derivedEstimatedTime | Corresponds to total work. Time a work package likely needs to be completed including itself and its descendants. | Duration | | READ | | - | remainingTime | Corresponds to remaining work. Remaining time a work package likely needs to be completed. | Duration | | READ / WRITE | | - | derivedRemainingTime | Corresponds to total remaining work. Remaining time a work package likely needs to be completed including itself and its descendants. | Duration | | READ | | - | ignoreNonWorkingDays | When scheduling, whether or not to ignore the non working days being defined. A work package with the flag set to true will be allowed to be scheduled to a non working day. | Boolean | Cannot be set for parent work packages unless it is scheduled manually | READ | | - | spentTime | The time booked for this work package by users working on it | Duration | | READ | **Permission** view time entries | - | percentageDone | Corresponds to % complete. Amount of total completion for a work package. | Integer | 0 <= x <= 100; can be null | READ | | - | derivedPercentageDone | Corresponds to total % complete. Amount of total completion for a work package and its descendants. | Integer | 0 <= x <= 100; can be null | READ | | - | readonly | If true, the work package is in a readonly status so with the exception of the status, no other property can be altered. | Boolean | | READ | Enterprise edition only | - | createdAt | Time of creation | DateTime | | READ | | - | updatedAt | Time of the most recent change to the work package | DateTime | | READ | | + | Property | Description | Type | Constraints | Supported operations | Condition | + | :--------------: | ------------------------------------------------------ | ----------- | ------------------------------------------------------------------------------------------------------ | -------------------- | -------------------------------- | + | id | Work package id | Integer | x > 0 | READ | | + | lockVersion | The version of the item as used for optimistic locking | Integer | | READ | | + | subject | Work package subject | String | not null; 1 <= length <= 255 | READ / WRITE | Is write protected if the type has automatic subject generation configured. | + | status | Name of the work package's status | String | not null | READ | | + | type | Name of the work package's type | String | not null | READ | | + | description | The work package description | Formattable | | READ / WRITE | | + | scheduleManually | Uses manual scheduling mode when true (default). Uses automatic scheduling mode when false. Can be automatic only when predecessors or children are present. | Boolean | | READ / WRITE | | + | startDate | Scheduled beginning of a work package | Date | Cannot be set for parent work packages unless it is scheduled manually; must be equal or greater than the earliest possible start date; Exists only on work packages of a non milestone type | READ / WRITE | | + | dueDate | Scheduled end of a work package | Date | Cannot be set for parent work packages unless it is scheduled manually; must be greater than or equal to the start date; Exists only on work packages of a non milestone type | READ / WRITE | | + | date | Date on which a milestone is achieved | Date | Exists only on work packages of a milestone type | READ / WRITE | | + | derivedStartDate | Similar to start date but is not set by a client but rather deduced by the work packages' descendants. If manual scheduleManually is active, the two dates can deviate. | Date | | READ | | + | derivedDueDate | Similar to due date but is not set by a client but rather deduced by the work packages' descendants. If manual scheduleManually is active, the two dates can deviate. | Date | | READ | | + | duration | The amount of time in hours the work package needs to be completed. | Duration | Not available for milestone type of work packages. | READ / WRITE | | + | estimatedTime | Corresponds to work. Time a work package likely needs to be completed. | Duration | | READ / WRITE | | + | derivedEstimatedTime | Corresponds to total work. Time a work package likely needs to be completed including itself and its descendants. | Duration | | READ | | + | remainingTime | Corresponds to remaining work. Remaining time a work package likely needs to be completed. | Duration | | READ / WRITE | | + | derivedRemainingTime | Corresponds to total remaining work. Remaining time a work package likely needs to be completed including itself and its descendants. | Duration | | READ | | + | ignoreNonWorkingDays | When scheduling, whether or not to ignore the non working days being defined. A work package with the flag set to true will be allowed to be scheduled to a non working day. | Boolean | Cannot be set for parent work packages unless it is scheduled manually | READ | | + | spentTime | The time booked for this work package by users working on it | Duration | | READ | **Permission** view time entries | + | percentageDone | Corresponds to % complete. Amount of total completion for a work package. | Integer | 0 <= x <= 100; can be null | READ | | + | derivedPercentageDone | Corresponds to total % complete. Amount of total completion for a work package and its descendants. | Integer | 0 <= x <= 100; can be null | READ | | + | readonly | If true, the work package is in a readonly status so with the exception of the status, no other property can be altered. | Boolean | | READ | Enterprise edition only | + | createdAt | Time of creation | DateTime | | READ | | + | updatedAt | Time of the most recent change to the work package | DateTime | | READ | | Note that the properties listed here only cover the built-in properties of the OpenProject Core. Using plug-ins and custom fields a work package might contain various additional properties. diff --git a/frontend/src/app/shared/components/fields/display/display-field.initializer.ts b/frontend/src/app/shared/components/fields/display/display-field.initializer.ts index c33d0b26de1..afdcd65bc76 100644 --- a/frontend/src/app/shared/components/fields/display/display-field.initializer.ts +++ b/frontend/src/app/shared/components/fields/display/display-field.initializer.ts @@ -89,6 +89,9 @@ import { LinkDisplayField } from 'core-app/shared/components/fields/display/fiel import { ProjectPhaseDisplayField, } from 'core-app/shared/components/fields/display/field-types/project-phase-display-field.module'; +import { + SubjectDisplayField, +} from 'core-app/shared/components/fields/display/field-types/subject-display-field.module'; export function initializeCoreDisplayFields(displayFieldService:DisplayFieldService) { return () => { @@ -130,6 +133,7 @@ export function initializeCoreDisplayFields(displayFieldService:DisplayFieldServ .addSpecificFieldType('WorkPackage', WorkPackageIdDisplayField, 'id', ['id']) .addSpecificFieldType('WorkPackage', WorkPackageSpentTimeDisplayField, 'spentTime', ['spentTime']) .addSpecificFieldType('WorkPackage', CombinedDateDisplayField, 'combinedDate', ['combinedDate']) + .addSpecificFieldType('WorkPackage', SubjectDisplayField, 'subject', ['String']) .addSpecificFieldType('TimeEntry', PlainFormattableDisplayField, 'comment', ['comment']) .addSpecificFieldType('Project', ProjectStatusDisplayField, 'status', ['status']) .addSpecificFieldType('TimeEntry', WorkPackageDisplayField, 'work_package', ['workPackage']); diff --git a/frontend/src/app/shared/components/fields/display/field-types/subject-display-field.module.ts b/frontend/src/app/shared/components/fields/display/field-types/subject-display-field.module.ts new file mode 100644 index 00000000000..8d003ad1655 --- /dev/null +++ b/frontend/src/app/shared/components/fields/display/field-types/subject-display-field.module.ts @@ -0,0 +1,41 @@ +//-- 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. +//++ + +import { TextDisplayField } from 'core-app/shared/components/fields/display/field-types/text-display-field.module'; +import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; + +export class SubjectDisplayField extends TextDisplayField { + public get placeholder():string { + // When subject is not writable, it means automatic subject is configured on the type. + if (!this.schema.writable) { + const typeName = (this.resource as WorkPackageResource).type?.name || ''; + return this.I18n.t('js.placeholders.subject_auto_generated', { type: typeName }); + } + return '-'; + } +} diff --git a/lib/api/v3/work_packages/schema/work_package_schema_representer.rb b/lib/api/v3/work_packages/schema/work_package_schema_representer.rb index 0bf958bf98a..3e8c845ca6e 100644 --- a/lib/api/v3/work_packages/schema/work_package_schema_representer.rb +++ b/lib/api/v3/work_packages/schema/work_package_schema_representer.rb @@ -114,7 +114,10 @@ module API schema :subject, type: "String", min_length: 1, - max_length: 255 + max_length: 255, + has_default: -> { + represented.type&.replacement_pattern_defined_for?(:subject) + } schema :description, type: "Formattable", diff --git a/spec/lib/api/v3/work_packages/schema/work_package_schema_representer_spec.rb b/spec/lib/api/v3/work_packages/schema/work_package_schema_representer_spec.rb index 200a12882e6..41edeb517b2 100644 --- a/spec/lib/api/v3/work_packages/schema/work_package_schema_representer_spec.rb +++ b/spec/lib/api/v3/work_packages/schema/work_package_schema_representer_spec.rb @@ -284,6 +284,23 @@ RSpec.describe API::V3::WorkPackages::Schema::WorkPackageSchemaRepresenter do let(:min_length) { 1 } let(:max_length) { 255 } end + + context "on a work package which's type has an auto-generated subject" do + before do + allow(wp_type) + .to receive(:replacement_pattern_defined_for?) + .with(:subject) + .and_return(true) + end + + it_behaves_like "has basic schema properties" do + let(:type) { "String" } + let(:name) { I18n.t("attributes.subject") } + let(:required) { true } + let(:writable) { false } + let(:has_default) { true } + end + end end describe "description" do @@ -1217,8 +1234,8 @@ RSpec.describe API::V3::WorkPackages::Schema::WorkPackageSchemaRepresenter do call_count = 0 allow(work_package.type) .to receive(:attribute_groups) do - call_count += 1 - [] + call_count += 1 + [] end # Rendering two times, the Type#attribute_groups diff --git a/spec/services/work_packages/set_attributes_service_spec.rb b/spec/services/work_packages/set_attributes_service_spec.rb index 84996a8014e..7e1332e3ed7 100644 --- a/spec/services/work_packages/set_attributes_service_spec.rb +++ b/spec/services/work_packages/set_attributes_service_spec.rb @@ -2282,20 +2282,40 @@ RSpec.describe WorkPackages::SetAttributesService, end end - context "when the type defines a pattern for an attribute" do + context "when the type defines a pattern for subject" do let(:type) { build_stubbed(:type, patterns: { subject: { blueprint: "{{type}} {{project_name}}", enabled: true } }) } - let(:work_package) { WorkPackage.new(type:) } + let(:work_package) { WorkPackage.new(type:, project:) } + let(:resolved_subject) { "#{type.name} #{project.name}" } + let(:pattern_resolver) { instance_double(WorkPackageTypes::PatternResolver, resolve: resolved_subject) } - it "assigns a placeholder value to the field when subject is blank" do - instance.call({}) - - expect(work_package.subject).to eq(I18n.t("work_packages.templated_subject_hint", type: type.name)) + before do + allow(WorkPackageTypes::PatternResolver).to receive(:new).and_return(pattern_resolver) end - it "does not override a passed subject" do - instance.call(subject: "My custom subject") + it "sets the resolved subject from the pattern" do + instance.call({}) - expect(work_package.subject).to eq("My custom subject") + expect(work_package.subject).to eq(resolved_subject) + end + + it "marks the subject change as changed by system" do + instance.call({}) + + expect(work_package.changed_by_system).to include("subject" => ["", resolved_subject]) + end + + context "when subject is overridden" do + it "keeps the overridden value" do + instance.call(subject: "My custom subject") + + expect(work_package.subject).to eq("My custom subject") + end + + it "does not mark subject as changed by system" do + instance.call(subject: "My custom subject") + + expect(work_package.changed_by_system).not_to include("subject") + end end context "when the pattern is disabled" do @@ -2303,43 +2323,11 @@ RSpec.describe WorkPackages::SetAttributesService, build_stubbed(:type, patterns: { subject: { blueprint: "{{type}} {{project_name}}", enabled: false } }) end - it "does not overwrite the attribute" do + it "does not auto-generate the subject" do instance.call(subject: "I will be kept") expect(work_package.subject).to eq("I will be kept") end end - - context "when the work package is persisted" do - let(:work_package) { build_stubbed(:work_package, type:, project:, subject: "Original subject") } - let(:resolved_subject) { "#{type.name} #{project.name}" } - let(:pattern_resolver) { instance_double(WorkPackageTypes::PatternResolver, resolve: resolved_subject) } - - before do - allow(WorkPackageTypes::PatternResolver).to receive(:new).and_return(pattern_resolver) - end - - it "does not override existing subject" do - instance.call({}) - - expect(work_package.subject).to eq("Original subject") - end - - context "when subject is explicitly cleared" do - it "sets the resolved subject from the pattern" do - work_package.subject = nil - instance.call({}) - - expect(work_package.subject).to eq(resolved_subject) - end - - it "marks the subject change as changed by system" do - work_package.subject = nil - instance.call({}) - - expect(work_package.changed_by_system).to include("subject" => [anything, resolved_subject]) - end - end - end end end From 97b6b94494bf11d66a440e0702fa08ff2bde70c4 Mon Sep 17 00:00:00 2001 From: ulferts Date: Tue, 3 Feb 2026 12:50:13 +0100 Subject: [PATCH 052/293] generalize subject solution in SetAttributesService --- .../work_packages/set_attributes_service.rb | 13 +++++++------ app/services/work_packages/update_service.rb | 15 --------------- .../work_packages/set_attributes_service_spec.rb | 8 ++++---- 3 files changed, 11 insertions(+), 25 deletions(-) diff --git a/app/services/work_packages/set_attributes_service.rb b/app/services/work_packages/set_attributes_service.rb index e2b35eec098..103880e66a3 100644 --- a/app/services/work_packages/set_attributes_service.rb +++ b/app/services/work_packages/set_attributes_service.rb @@ -49,15 +49,16 @@ class WorkPackages::SetAttributesService < BaseServices::SetAttributes set_custom_values_to_validate(attributes, validate_custom_fields) # Needs to be late so that updates to values can be taken into account. - set_templated_subject + model.change_by_system do + set_templated_attributes + end end - def set_templated_subject - return unless work_package.type&.replacement_pattern_defined_for?(:subject) - return if work_package.subject_changed? + def set_templated_attributes + model.type&.enabled_patterns&.each do |key, pattern| + next if model.changed_attribute_keys.include?(key) - model.change_by_system do - work_package.subject = work_package.type.enabled_patterns[:subject].resolve(work_package) + model.public_send(:"#{key}=", pattern.resolve(model)) end end diff --git a/app/services/work_packages/update_service.rb b/app/services/work_packages/update_service.rb index 28fc88c747c..ffd13e054a3 100644 --- a/app/services/work_packages/update_service.rb +++ b/app/services/work_packages/update_service.rb @@ -41,22 +41,7 @@ class WorkPackages::UpdateService < BaseServices::Update private - # TODO: check if this can be removed as currently, only the subject - # can be updated automatically anyway and that is handled in the SetAttributesService. - def set_templated_attributes - # Subject is handled separately in SetAttributesService. - # Other templated attributes are set here. - # TODO: code smell here: saving the automatically generated attributes depends - # on running the UpdateAncestorsService right after. The attributes get saved - # only thanks to this. If the UpdateAncestorsService is not run, the attributes - # are not saved. That's an odd coupling. - model.type.enabled_patterns.except(:subject).each do |key, pattern| - model.public_send(:"#{key}=", pattern.resolve(model)) - end - end - def after_perform(service_call) - set_templated_attributes update_related_work_packages(service_call) cleanup(service_call.result) diff --git a/spec/services/work_packages/set_attributes_service_spec.rb b/spec/services/work_packages/set_attributes_service_spec.rb index 7e1332e3ed7..3c341046785 100644 --- a/spec/services/work_packages/set_attributes_service_spec.rb +++ b/spec/services/work_packages/set_attributes_service_spec.rb @@ -2286,10 +2286,10 @@ RSpec.describe WorkPackages::SetAttributesService, let(:type) { build_stubbed(:type, patterns: { subject: { blueprint: "{{type}} {{project_name}}", enabled: true } }) } let(:work_package) { WorkPackage.new(type:, project:) } let(:resolved_subject) { "#{type.name} #{project.name}" } - let(:pattern_resolver) { instance_double(WorkPackageTypes::PatternResolver, resolve: resolved_subject) } - - before do - allow(WorkPackageTypes::PatternResolver).to receive(:new).and_return(pattern_resolver) + let(:pattern_resolver) do + instance_double(WorkPackageTypes::PatternResolver, resolve: resolved_subject).tap do |resolver| + allow(WorkPackageTypes::PatternResolver).to receive(:new).and_return(resolver) + end end it "sets the resolved subject from the pattern" do From edeca04c891717da8b5ab9c6bd620f147bcfec57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Tue, 3 Feb 2026 12:28:23 +0100 Subject: [PATCH 053/293] Add cooldown to dependabot --- .github/dependabot.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b83f67934d8..cd74bbbae83 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,6 +5,11 @@ updates: schedule: interval: "daily" target-branch: "dev" + cooldown: + default-days: 5 + semver-major-days: 30 + semver-minor-days: 14 + semver-patch-days: 5 groups: angular: patterns: @@ -40,6 +45,11 @@ updates: schedule: interval: "daily" target-branch: "dev" + cooldown: + default-days: 5 + semver-major-days: 30 + semver-minor-days: 14 + semver-patch-days: 5 groups: aws-gems: patterns: From b8f56be172e11906a0336c4333e27a4fa8c3b168 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 10:37:18 -0300 Subject: [PATCH 054/293] Bump @typescript-eslint/parser from 8.52.0 to 8.53.1 in /frontend (#21857) Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.52.0 to 8.53.1. - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.53.1/packages/parser) --- updated-dependencies: - dependency-name: "@typescript-eslint/parser" dependency-version: 8.53.1 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- frontend/package-lock.json | 149 +++++++++++++++++-------------------- frontend/package.json | 2 +- 2 files changed, 70 insertions(+), 81 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index fd437e256c5..ff120ceea19 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -161,7 +161,7 @@ "@types/uuid": "^11.0.0", "@types/webpack-env": "^1.16.0", "@typescript-eslint/eslint-plugin": "8.53.0", - "@typescript-eslint/parser": "8.52.0", + "@typescript-eslint/parser": "8.53.1", "angular-eslint": "^21.1.0", "browserslist": "^4.28.1", "eslint": "^9.39.2", @@ -9828,15 +9828,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.52.0.tgz", - "integrity": "sha512-iIACsx8pxRnguSYhHiMn2PvhvfpopO9FXHyn1mG5txZIsAaB6F0KwbFnUQN3KCiG3Jcuad/Cao2FAs1Wp7vAyg==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.53.1.tgz", + "integrity": "sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.52.0", - "@typescript-eslint/types": "8.52.0", - "@typescript-eslint/typescript-estree": "8.52.0", - "@typescript-eslint/visitor-keys": "8.52.0", + "@typescript-eslint/scope-manager": "8.53.1", + "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/typescript-estree": "8.53.1", + "@typescript-eslint/visitor-keys": "8.53.1", "debug": "^4.4.3" }, "engines": { @@ -9869,14 +9869,13 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.52.0.tgz", - "integrity": "sha512-xD0MfdSdEmeFa3OmVqonHi+Cciab96ls1UhIF/qX/O/gPu5KXD0bY9lu33jj04fjzrXHcuvjBcBC+D3SNSadaw==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.53.1.tgz", + "integrity": "sha512-WYC4FB5Ra0xidsmlPb+1SsnaSKPmS3gsjIARwbEkHkoWloQmuzcfypljaJcR78uyLA1h8sHdWWPHSLDI+MtNog==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.52.0", - "@typescript-eslint/types": "^8.52.0", + "@typescript-eslint/tsconfig-utils": "^8.53.1", + "@typescript-eslint/types": "^8.53.1", "debug": "^4.4.3" }, "engines": { @@ -9895,7 +9894,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, - "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -9909,14 +9907,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.52.0.tgz", - "integrity": "sha512-ixxqmmCcc1Nf8S0mS0TkJ/3LKcC8mruYJPOU6Ia2F/zUUR4pApW7LzrpU3JmtePbRUTes9bEqRc1Gg4iyRnDzA==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.1.tgz", + "integrity": "sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.52.0", - "@typescript-eslint/visitor-keys": "8.52.0" + "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/visitor-keys": "8.53.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -9927,11 +9924,10 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.52.0.tgz", - "integrity": "sha512-jl+8fzr/SdzdxWJznq5nvoI7qn2tNYV/ZBAEcaFMVXf+K6jmXvAFrgo/+5rxgnL152f//pDEAYAhhBAZGrVfwg==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.1.tgz", + "integrity": "sha512-qfvLXS6F6b1y43pnf0pPbXJ+YoXIC7HKg0UGZ27uMIemKMKA6XH2DTxsEDdpdN29D+vHV07x/pnlPNVLhdhWiA==", "dev": true, - "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -10155,11 +10151,10 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.52.0.tgz", - "integrity": "sha512-LWQV1V4q9V4cT4H5JCIx3481iIFxH1UkVk+ZkGGAV1ZGcjGI9IoFOfg3O6ywz8QqCDEp7Inlg6kovMofsNRaGg==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.1.tgz", + "integrity": "sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==", "dev": true, - "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -10169,16 +10164,15 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.52.0.tgz", - "integrity": "sha512-XP3LClsCc0FsTK5/frGjolyADTh3QmsLp6nKd476xNI9CsSsLnmn4f0jrzNoAulmxlmNIpeXuHYeEQv61Q6qeQ==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.1.tgz", + "integrity": "sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.52.0", - "@typescript-eslint/tsconfig-utils": "8.52.0", - "@typescript-eslint/types": "8.52.0", - "@typescript-eslint/visitor-keys": "8.52.0", + "@typescript-eslint/project-service": "8.53.1", + "@typescript-eslint/tsconfig-utils": "8.53.1", + "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/visitor-keys": "8.53.1", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", @@ -10201,7 +10195,6 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -10211,7 +10204,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, - "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -10229,7 +10221,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -10428,13 +10419,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.52.0.tgz", - "integrity": "sha512-ink3/Zofus34nmBsPjow63FP5M7IGff0RKAgqR6+CFpdk22M7aLwC9gOcLGYqr7MczLPzZVERW9hRog3O4n1sQ==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.1.tgz", + "integrity": "sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.52.0", + "@typescript-eslint/types": "8.53.1", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -10450,7 +10440,6 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -31757,15 +31746,15 @@ } }, "@typescript-eslint/parser": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.52.0.tgz", - "integrity": "sha512-iIACsx8pxRnguSYhHiMn2PvhvfpopO9FXHyn1mG5txZIsAaB6F0KwbFnUQN3KCiG3Jcuad/Cao2FAs1Wp7vAyg==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.53.1.tgz", + "integrity": "sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "8.52.0", - "@typescript-eslint/types": "8.52.0", - "@typescript-eslint/typescript-estree": "8.52.0", - "@typescript-eslint/visitor-keys": "8.52.0", + "@typescript-eslint/scope-manager": "8.53.1", + "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/typescript-estree": "8.53.1", + "@typescript-eslint/visitor-keys": "8.53.1", "debug": "^4.4.3" }, "dependencies": { @@ -31781,13 +31770,13 @@ } }, "@typescript-eslint/project-service": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.52.0.tgz", - "integrity": "sha512-xD0MfdSdEmeFa3OmVqonHi+Cciab96ls1UhIF/qX/O/gPu5KXD0bY9lu33jj04fjzrXHcuvjBcBC+D3SNSadaw==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.53.1.tgz", + "integrity": "sha512-WYC4FB5Ra0xidsmlPb+1SsnaSKPmS3gsjIARwbEkHkoWloQmuzcfypljaJcR78uyLA1h8sHdWWPHSLDI+MtNog==", "dev": true, "requires": { - "@typescript-eslint/tsconfig-utils": "^8.52.0", - "@typescript-eslint/types": "^8.52.0", + "@typescript-eslint/tsconfig-utils": "^8.53.1", + "@typescript-eslint/types": "^8.53.1", "debug": "^4.4.3" }, "dependencies": { @@ -31803,19 +31792,19 @@ } }, "@typescript-eslint/scope-manager": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.52.0.tgz", - "integrity": "sha512-ixxqmmCcc1Nf8S0mS0TkJ/3LKcC8mruYJPOU6Ia2F/zUUR4pApW7LzrpU3JmtePbRUTes9bEqRc1Gg4iyRnDzA==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.1.tgz", + "integrity": "sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ==", "dev": true, "requires": { - "@typescript-eslint/types": "8.52.0", - "@typescript-eslint/visitor-keys": "8.52.0" + "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/visitor-keys": "8.53.1" } }, "@typescript-eslint/tsconfig-utils": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.52.0.tgz", - "integrity": "sha512-jl+8fzr/SdzdxWJznq5nvoI7qn2tNYV/ZBAEcaFMVXf+K6jmXvAFrgo/+5rxgnL152f//pDEAYAhhBAZGrVfwg==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.1.tgz", + "integrity": "sha512-qfvLXS6F6b1y43pnf0pPbXJ+YoXIC7HKg0UGZ27uMIemKMKA6XH2DTxsEDdpdN29D+vHV07x/pnlPNVLhdhWiA==", "dev": true }, "@typescript-eslint/type-utils": { @@ -31939,21 +31928,21 @@ } }, "@typescript-eslint/types": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.52.0.tgz", - "integrity": "sha512-LWQV1V4q9V4cT4H5JCIx3481iIFxH1UkVk+ZkGGAV1ZGcjGI9IoFOfg3O6ywz8QqCDEp7Inlg6kovMofsNRaGg==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.1.tgz", + "integrity": "sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.52.0.tgz", - "integrity": "sha512-XP3LClsCc0FsTK5/frGjolyADTh3QmsLp6nKd476xNI9CsSsLnmn4f0jrzNoAulmxlmNIpeXuHYeEQv61Q6qeQ==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.1.tgz", + "integrity": "sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg==", "dev": true, "requires": { - "@typescript-eslint/project-service": "8.52.0", - "@typescript-eslint/tsconfig-utils": "8.52.0", - "@typescript-eslint/types": "8.52.0", - "@typescript-eslint/visitor-keys": "8.52.0", + "@typescript-eslint/project-service": "8.53.1", + "@typescript-eslint/tsconfig-utils": "8.53.1", + "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/visitor-keys": "8.53.1", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", @@ -32098,12 +32087,12 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.52.0.tgz", - "integrity": "sha512-ink3/Zofus34nmBsPjow63FP5M7IGff0RKAgqR6+CFpdk22M7aLwC9gOcLGYqr7MczLPzZVERW9hRog3O4n1sQ==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.1.tgz", + "integrity": "sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==", "dev": true, "requires": { - "@typescript-eslint/types": "8.52.0", + "@typescript-eslint/types": "8.53.1", "eslint-visitor-keys": "^4.2.1" }, "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index 4b35686fcfe..8edea1ea874 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -38,7 +38,7 @@ "@types/uuid": "^11.0.0", "@types/webpack-env": "^1.16.0", "@typescript-eslint/eslint-plugin": "8.53.0", - "@typescript-eslint/parser": "8.52.0", + "@typescript-eslint/parser": "8.53.1", "angular-eslint": "^21.1.0", "browserslist": "^4.28.1", "eslint": "^9.39.2", From 0f1c7837429bc452fabfba8f2b40c4a9d812de6b Mon Sep 17 00:00:00 2001 From: ulferts Date: Tue, 3 Feb 2026 17:18:08 +0100 Subject: [PATCH 055/293] move applying patterns into the update/create service --- app/services/types/apply_patterns.rb | 49 +++++++++++++++++++ app/services/work_packages/create_service.rb | 3 ++ .../work_packages/set_attributes_service.rb | 13 ----- app/services/work_packages/update_service.rb | 6 +++ .../set_attributes_service_spec.rb | 37 +++----------- .../update_service_integration_spec.rb | 14 ++++++ 6 files changed, 78 insertions(+), 44 deletions(-) create mode 100644 app/services/types/apply_patterns.rb diff --git a/app/services/types/apply_patterns.rb b/app/services/types/apply_patterns.rb new file mode 100644 index 00000000000..b01490549d3 --- /dev/null +++ b/app/services/types/apply_patterns.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +# ++ + +module Types + module ApplyPatterns + extend ActiveSupport::Concern + + included do + private + + def apply_patterns(model, save: true) + model.type&.enabled_patterns&.each do |key, pattern| + next if model.changed_attribute_keys.include?(key) + + model.public_send(:"#{key}=", pattern.resolve(model)) + end + + model.save!(validate: false) if save && model.changed? + end + end + end +end diff --git a/app/services/work_packages/create_service.rb b/app/services/work_packages/create_service.rb index 68d7461cf22..41500a9defa 100644 --- a/app/services/work_packages/create_service.rb +++ b/app/services/work_packages/create_service.rb @@ -31,6 +31,7 @@ class WorkPackages::CreateService < BaseServices::BaseCallable include ::WorkPackages::Shared::UpdateAncestors include ::Shared::ServiceContext + include Types::ApplyPatterns attr_reader :user, :contract_class, :contract_options @@ -61,6 +62,8 @@ class WorkPackages::CreateService < BaseServices::BaseCallable work_package.attachments = work_package.attachments_replacements if work_package.attachments_replacements work_package.save(validate: false) + apply_patterns(work_package) + # update ancestors before rescheduling, as the parent might switch to automatic mode multi_update_ancestors(result.all_results).each do |ancestor_result| result.merge!(ancestor_result) diff --git a/app/services/work_packages/set_attributes_service.rb b/app/services/work_packages/set_attributes_service.rb index 103880e66a3..2d1844b9313 100644 --- a/app/services/work_packages/set_attributes_service.rb +++ b/app/services/work_packages/set_attributes_service.rb @@ -47,19 +47,6 @@ class WorkPackages::SetAttributesService < BaseServices::SetAttributes set_custom_attributes(attributes) set_custom_values_to_validate(attributes, validate_custom_fields) - - # Needs to be late so that updates to values can be taken into account. - model.change_by_system do - set_templated_attributes - end - end - - def set_templated_attributes - model.type&.enabled_patterns&.each do |key, pattern| - next if model.changed_attribute_keys.include?(key) - - model.public_send(:"#{key}=", pattern.resolve(model)) - end end def set_custom_values_to_validate(attributes, validate_custom_fields = nil) diff --git a/app/services/work_packages/update_service.rb b/app/services/work_packages/update_service.rb index ffd13e054a3..8b16cda7045 100644 --- a/app/services/work_packages/update_service.rb +++ b/app/services/work_packages/update_service.rb @@ -31,6 +31,7 @@ class WorkPackages::UpdateService < BaseServices::Update include ::WorkPackages::Shared::UpdateAncestors include Attachments::ReplaceAttachments + include Types::ApplyPatterns attr_accessor :cause_of_rescheduling @@ -42,6 +43,11 @@ class WorkPackages::UpdateService < BaseServices::Update private def after_perform(service_call) + # TODO: code smell here: saving the automatically generated subject depends + # on running the UpdateAncestorsService right after. The subject gets saved + # only thanks to this. If the UpdateAncestorsService is not run, the subject + # is not saved. That's an odd coupling. + apply_patterns(service_call.result, save: false) update_related_work_packages(service_call) cleanup(service_call.result) diff --git a/spec/services/work_packages/set_attributes_service_spec.rb b/spec/services/work_packages/set_attributes_service_spec.rb index 3c341046785..f38cf355ac3 100644 --- a/spec/services/work_packages/set_attributes_service_spec.rb +++ b/spec/services/work_packages/set_attributes_service_spec.rb @@ -2292,42 +2292,17 @@ RSpec.describe WorkPackages::SetAttributesService, end end - it "sets the resolved subject from the pattern" do + # Testing this because the behaviour used to be different. + it "does not set the resolved subject from the pattern" do instance.call({}) - expect(work_package.subject).to eq(resolved_subject) + expect(work_package.subject).to be_blank end - it "marks the subject change as changed by system" do - instance.call({}) + it "keeps an overridden subject" do + instance.call(subject: "My custom subject") - expect(work_package.changed_by_system).to include("subject" => ["", resolved_subject]) - end - - context "when subject is overridden" do - it "keeps the overridden value" do - instance.call(subject: "My custom subject") - - expect(work_package.subject).to eq("My custom subject") - end - - it "does not mark subject as changed by system" do - instance.call(subject: "My custom subject") - - expect(work_package.changed_by_system).not_to include("subject") - end - end - - context "when the pattern is disabled" do - let(:type) do - build_stubbed(:type, patterns: { subject: { blueprint: "{{type}} {{project_name}}", enabled: false } }) - end - - it "does not auto-generate the subject" do - instance.call(subject: "I will be kept") - - expect(work_package.subject).to eq("I will be kept") - end + expect(work_package.subject).to eq("My custom subject") end end end diff --git a/spec/services/work_packages/update_service_integration_spec.rb b/spec/services/work_packages/update_service_integration_spec.rb index 166292e33ba..7a8027c422f 100644 --- a/spec/services/work_packages/update_service_integration_spec.rb +++ b/spec/services/work_packages/update_service_integration_spec.rb @@ -1758,6 +1758,20 @@ RSpec.describe WorkPackages::UpdateService, "integration", type: :model do subject: "##{work_package.id} by #{user.name} - #{default_status.name}" ) end + + context "when no attribute is changed" do + let(:attributes) { {} } + + before do + work_package.subject = autosubject_type.enabled_patterns[:subject].resolve(work_package) + work_package.save! + end + + it "does not lead to a new journal entry" do + expect { subject } + .not_to change { work_package.journals.count } + end + end end describe "replacing the attachments" do From b834859aab558400b58829a8a1c93ea162f58ffb Mon Sep 17 00:00:00 2001 From: ulferts Date: Tue, 3 Feb 2026 17:38:29 +0100 Subject: [PATCH 056/293] don't apply presence validation in case the subject is autogenerated --- app/contracts/work_packages/base_contract.rb | 5 ++ app/models/work_package/validations.rb | 4 +- .../work_packages/create_contract_spec.rb | 2 +- ...ontract.rb => shared_contract_examples.rb} | 51 +++++++++++++++---- .../work_packages/update_contract_spec.rb | 2 +- 5 files changed, 48 insertions(+), 16 deletions(-) rename spec/contracts/work_packages/{shared_base_contract.rb => shared_contract_examples.rb} (86%) diff --git a/app/contracts/work_packages/base_contract.rb b/app/contracts/work_packages/base_contract.rb index 5a25d556bb4..17599903f5c 100644 --- a/app/contracts/work_packages/base_contract.rb +++ b/app/contracts/work_packages/base_contract.rb @@ -134,6 +134,11 @@ module WorkPackages attribute :budget + validates :subject, + presence: true, + unless: -> { model.type&.replacement_pattern_defined_for?(:subject) } + validates :subject, length: { maximum: 255 } + validates :due_date, date: { after_or_equal_to: :start_date, message: :greater_than_or_equal_to_start_date, diff --git a/app/models/work_package/validations.rb b/app/models/work_package/validations.rb index 01102c87289..990bf402e88 100644 --- a/app/models/work_package/validations.rb +++ b/app/models/work_package/validations.rb @@ -32,9 +32,7 @@ module WorkPackage::Validations extend ActiveSupport::Concern included do - validates :subject, :priority, :project, :type, :author, :status, presence: true - - validates :subject, length: { maximum: 255 } + validates :priority, :project, :type, :author, :status, presence: true validates :done_ratio, inclusion: { in: 0..100 }, numericality: true, allow_nil: true validates :estimated_hours, numericality: { allow_nil: true, greater_than_or_equal_to: 0 } validates :remaining_hours, numericality: { allow_nil: true, greater_than_or_equal_to: 0 } diff --git a/spec/contracts/work_packages/create_contract_spec.rb b/spec/contracts/work_packages/create_contract_spec.rb index fe9104d5cad..3c98050ff68 100644 --- a/spec/contracts/work_packages/create_contract_spec.rb +++ b/spec/contracts/work_packages/create_contract_spec.rb @@ -29,7 +29,7 @@ #++ require "spec_helper" -require "contracts/work_packages/shared_base_contract" +require "contracts/work_packages/shared_contract_examples" RSpec.describe WorkPackages::CreateContract do include_context "work package contract" diff --git a/spec/contracts/work_packages/shared_base_contract.rb b/spec/contracts/work_packages/shared_contract_examples.rb similarity index 86% rename from spec/contracts/work_packages/shared_base_contract.rb rename to spec/contracts/work_packages/shared_contract_examples.rb index aa0b534eece..b580582886e 100644 --- a/spec/contracts/work_packages/shared_base_contract.rb +++ b/spec/contracts/work_packages/shared_contract_examples.rb @@ -35,7 +35,10 @@ RSpec.shared_examples "work package contract" do include_context "ModelContract shared context" shared_let(:persisted_type) { create(:type) } - shared_let(:persisted_project) { create(:project, types: [persisted_type]) } + shared_let(:persisted_type_with_pattern) do + create(:type, patterns: { subject: { blueprint: "{{type}} {{project_name}}", enabled: true } }) + end + shared_let(:persisted_project) { create(:project, types: [persisted_type, persisted_type_with_pattern]) } shared_let(:persisted_other_project) { create(:project, types: [persisted_type]) } shared_let(:persisted_project_version) { create(:version, project: persisted_project) } shared_let(:persisted_other_project_version) { create(:version) } @@ -82,7 +85,7 @@ RSpec.shared_examples "work package contract" do end describe "subject" do - context "when the type has no replacement pattern for subject" do + context "when the type is set" do before do work_package.subject = "Allowed to change subject" end @@ -90,27 +93,53 @@ RSpec.shared_examples "work package contract" do it_behaves_like "contract is valid" end - context "when the type has an enabled replacement pattern for subject" do - let(:type_with_pattern) do - create(:type, patterns: { subject: { blueprint: "{{type}} {{project_name}}", enabled: true } }) + context "when subject is blank and type does not auto-generate subject" do + before do + work_package.subject = "" end + it_behaves_like "contract is invalid", subject: :blank + end + + context "when the subject is changed and the type has an enabled replacement pattern for subject" do before do - work_package.project.types << type_with_pattern - work_package.type = type_with_pattern + work_package.type = persisted_type_with_pattern work_package.subject = "Trying to change subject" end it_behaves_like "contract is invalid", subject: :error_readonly end - context "when the type has a disabled replacement pattern for subject" do - let(:type_with_disabled_pattern) do - create(:type, patterns: { subject: { blueprint: "{{type}} {{project_name}}", enabled: false } }) + context "when subject is blank and type auto-generates subject" do + let(:type_with_pattern) do + create(:type, patterns: { subject: { blueprint: "{{type}} {{project_name}}", enabled: true } }) + end + + before do + # The type auto generates the subject. + # Therefore, it is ok that when creating the work package, the subject is empty. + # It will be set by the services before saving. + # Setting subject is not allowed when auto generating (read-only), which is why the spec works around that. + work_package.extend(OpenProject::ChangedBySystem) + + work_package.change_by_system do + work_package.subject = "" + end + + work_package.type = persisted_type_with_pattern + end + + it_behaves_like "contract is valid" + end + + context "when the type has a disabled replacement pattern for subject" do + let(:type_with_disabled_pattern) do + create(:type, patterns: { subject: { blueprint: "{{type}} {{project_name}}", enabled: false } }) do |type| + work_package.project.types << type + end end before do - work_package.project.types << type_with_disabled_pattern work_package.type = type_with_disabled_pattern work_package.subject = "Allowed to change subject" end diff --git a/spec/contracts/work_packages/update_contract_spec.rb b/spec/contracts/work_packages/update_contract_spec.rb index bdee5f002c0..bab6d8a4784 100644 --- a/spec/contracts/work_packages/update_contract_spec.rb +++ b/spec/contracts/work_packages/update_contract_spec.rb @@ -28,7 +28,7 @@ # See COPYRIGHT and LICENSE files for more details. require "spec_helper" -require "contracts/work_packages/shared_base_contract" +require "contracts/work_packages/shared_contract_examples" RSpec.describe WorkPackages::UpdateContract do include_context "work package contract" From f7a225b91418093162053bbfa6cbea6003db28a6 Mon Sep 17 00:00:00 2001 From: ulferts Date: Tue, 3 Feb 2026 18:14:08 +0100 Subject: [PATCH 057/293] remove empty default from work packages table --- ...move_default_from_work_packages_subject.rb | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 db/migrate/20260203171223_remove_default_from_work_packages_subject.rb diff --git a/db/migrate/20260203171223_remove_default_from_work_packages_subject.rb b/db/migrate/20260203171223_remove_default_from_work_packages_subject.rb new file mode 100644 index 00000000000..98c6748854f --- /dev/null +++ b/db/migrate/20260203171223_remove_default_from_work_packages_subject.rb @@ -0,0 +1,35 @@ +# 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 RemoveDefaultFromWorkPackagesSubject < ActiveRecord::Migration[8.0] + def change + change_column_default :work_packages, :subject, from: "", to: nil + end +end From 03f36fa3df979b93d7f1d4cd0a674fd3970a27b7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 20:29:03 -0300 Subject: [PATCH 058/293] Bump @isaacs/brace-expansion from 5.0.0 to 5.0.1 in /frontend (#21865) Bumps @isaacs/brace-expansion from 5.0.0 to 5.0.1. --- updated-dependencies: - dependency-name: "@isaacs/brace-expansion" dependency-version: 5.0.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- frontend/package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index ff120ceea19..182c1422e8a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -5719,9 +5719,9 @@ } }, "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", + "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", "dependencies": { "@isaacs/balanced-match": "^4.0.1" }, @@ -29219,9 +29219,9 @@ "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==" }, "@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", + "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", "requires": { "@isaacs/balanced-match": "^4.0.1" } From 59f71ab66b8bb7880b0a47628a57fd635e2dd68e Mon Sep 17 00:00:00 2001 From: OpenProject Actions CI Date: Wed, 4 Feb 2026 03:50:16 +0000 Subject: [PATCH 059/293] update locales from crowdin [ci skip] --- config/locales/crowdin/zh-TW.yml | 4 ++-- modules/meeting/config/locales/crowdin/af.yml | 4 +++- modules/meeting/config/locales/crowdin/ar.yml | 4 +++- modules/meeting/config/locales/crowdin/az.yml | 4 +++- modules/meeting/config/locales/crowdin/be.yml | 4 +++- modules/meeting/config/locales/crowdin/bg.yml | 4 +++- modules/meeting/config/locales/crowdin/ca.yml | 4 +++- modules/meeting/config/locales/crowdin/ckb-IR.yml | 4 +++- modules/meeting/config/locales/crowdin/cs.yml | 4 +++- modules/meeting/config/locales/crowdin/da.yml | 4 +++- modules/meeting/config/locales/crowdin/de.yml | 4 +++- modules/meeting/config/locales/crowdin/el.yml | 4 +++- modules/meeting/config/locales/crowdin/eo.yml | 4 +++- modules/meeting/config/locales/crowdin/es.yml | 4 +++- modules/meeting/config/locales/crowdin/et.yml | 4 +++- modules/meeting/config/locales/crowdin/eu.yml | 4 +++- modules/meeting/config/locales/crowdin/fa.yml | 4 +++- modules/meeting/config/locales/crowdin/fi.yml | 4 +++- modules/meeting/config/locales/crowdin/fil.yml | 4 +++- modules/meeting/config/locales/crowdin/fr.yml | 4 +++- modules/meeting/config/locales/crowdin/he.yml | 4 +++- modules/meeting/config/locales/crowdin/hi.yml | 4 +++- modules/meeting/config/locales/crowdin/hr.yml | 4 +++- modules/meeting/config/locales/crowdin/hu.yml | 4 +++- modules/meeting/config/locales/crowdin/id.yml | 4 +++- modules/meeting/config/locales/crowdin/it.yml | 4 +++- modules/meeting/config/locales/crowdin/ja.yml | 4 +++- modules/meeting/config/locales/crowdin/ka.yml | 4 +++- modules/meeting/config/locales/crowdin/kk.yml | 4 +++- modules/meeting/config/locales/crowdin/ko.yml | 4 +++- modules/meeting/config/locales/crowdin/lt.yml | 4 +++- modules/meeting/config/locales/crowdin/lv.yml | 4 +++- modules/meeting/config/locales/crowdin/mn.yml | 4 +++- modules/meeting/config/locales/crowdin/ms.yml | 4 +++- modules/meeting/config/locales/crowdin/ne.yml | 4 +++- modules/meeting/config/locales/crowdin/nl.yml | 4 +++- modules/meeting/config/locales/crowdin/no.yml | 4 +++- modules/meeting/config/locales/crowdin/pl.yml | 4 +++- modules/meeting/config/locales/crowdin/pt-BR.yml | 4 +++- modules/meeting/config/locales/crowdin/pt-PT.yml | 4 +++- modules/meeting/config/locales/crowdin/ro.yml | 4 +++- modules/meeting/config/locales/crowdin/ru.yml | 4 +++- modules/meeting/config/locales/crowdin/rw.yml | 4 +++- modules/meeting/config/locales/crowdin/si.yml | 4 +++- modules/meeting/config/locales/crowdin/sk.yml | 4 +++- modules/meeting/config/locales/crowdin/sl.yml | 4 +++- modules/meeting/config/locales/crowdin/sr.yml | 4 +++- modules/meeting/config/locales/crowdin/sv.yml | 4 +++- modules/meeting/config/locales/crowdin/th.yml | 4 +++- modules/meeting/config/locales/crowdin/tr.yml | 4 +++- modules/meeting/config/locales/crowdin/uk.yml | 4 +++- modules/meeting/config/locales/crowdin/uz.yml | 4 +++- modules/meeting/config/locales/crowdin/vi.yml | 4 +++- modules/meeting/config/locales/crowdin/zh-CN.yml | 4 +++- modules/meeting/config/locales/crowdin/zh-TW.yml | 4 +++- 55 files changed, 164 insertions(+), 56 deletions(-) diff --git a/config/locales/crowdin/zh-TW.yml b/config/locales/crowdin/zh-TW.yml index 17e798cbb8d..358f4f01b11 100644 --- a/config/locales/crowdin/zh-TW.yml +++ b/config/locales/crowdin/zh-TW.yml @@ -2417,7 +2417,7 @@ zh-TW: one_drive_sharepoint_file_storage: OneDrive/SharePoint 檔案儲存 placeholder_users: 佔位符使用者 portfolio_management: 組合管理 - project_creation_wizard: Project initiation request + project_creation_wizard: 專案啟動請求 project_list_sharing: 專案清單分享 readonly_work_packages: 唯讀工作套件 scim_api: SCIM 伺服器 API @@ -2503,7 +2503,7 @@ zh-TW: virus_scanning: description: "確保在其他使用者存取 OpenProject 中上傳的檔案前,對其進行病毒掃描。" project_creation_wizard: - description: "Generate a step-by-step wizard to help project managers fill out a project initiation request." + description: "產生一個逐步引導的精靈(Wizard),協助專案經理填寫專案啟動申請。" placeholder_users: title: 佔位符使用者 description: > diff --git a/modules/meeting/config/locales/crowdin/af.yml b/modules/meeting/config/locales/crowdin/af.yml index a87008dd82d..d1f2fae1aa6 100644 --- a/modules/meeting/config/locales/crowdin/af.yml +++ b/modules/meeting/config/locales/crowdin/af.yml @@ -611,7 +611,9 @@ af: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "This work package is not scheduled in an upcoming meeting agenda yet." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Use the "Add to meeting" button to add this work package to an upcoming meeting.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/ar.yml b/modules/meeting/config/locales/crowdin/ar.yml index 23e47512e96..05f1a2dee98 100644 --- a/modules/meeting/config/locales/crowdin/ar.yml +++ b/modules/meeting/config/locales/crowdin/ar.yml @@ -639,7 +639,9 @@ ar: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "This work package is not scheduled in an upcoming meeting agenda yet." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Use the "Add to meeting" button to add this work package to an upcoming meeting.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/az.yml b/modules/meeting/config/locales/crowdin/az.yml index e7172d3c3eb..73c30946f6c 100644 --- a/modules/meeting/config/locales/crowdin/az.yml +++ b/modules/meeting/config/locales/crowdin/az.yml @@ -611,7 +611,9 @@ az: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "This work package is not scheduled in an upcoming meeting agenda yet." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Use the "Add to meeting" button to add this work package to an upcoming meeting.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/be.yml b/modules/meeting/config/locales/crowdin/be.yml index a469dff7994..ac935ca2635 100644 --- a/modules/meeting/config/locales/crowdin/be.yml +++ b/modules/meeting/config/locales/crowdin/be.yml @@ -625,7 +625,9 @@ be: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "This work package is not scheduled in an upcoming meeting agenda yet." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Use the "Add to meeting" button to add this work package to an upcoming meeting.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/bg.yml b/modules/meeting/config/locales/crowdin/bg.yml index 90ad857d27c..5c809f92c27 100644 --- a/modules/meeting/config/locales/crowdin/bg.yml +++ b/modules/meeting/config/locales/crowdin/bg.yml @@ -611,7 +611,9 @@ bg: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "Този работен пакет все още не е включен в дневния ред на предстоящите срещи." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Използвайте бутона "Добавяне към среща", за да добавите този работен пакет към предстояща среща.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/ca.yml b/modules/meeting/config/locales/crowdin/ca.yml index 0b00c4f47bf..5edcbe60136 100644 --- a/modules/meeting/config/locales/crowdin/ca.yml +++ b/modules/meeting/config/locales/crowdin/ca.yml @@ -611,7 +611,9 @@ ca: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "Aquest paquet de treball encara no està planejat en cap agenda de reunió futura." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Utilitza el botó "Afegir a la reunió" per afegir aquest paquet de treball a una reunió futura.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/ckb-IR.yml b/modules/meeting/config/locales/crowdin/ckb-IR.yml index 837743a2c46..ae03b812f35 100644 --- a/modules/meeting/config/locales/crowdin/ckb-IR.yml +++ b/modules/meeting/config/locales/crowdin/ckb-IR.yml @@ -611,7 +611,9 @@ ckb-IR: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "This work package is not scheduled in an upcoming meeting agenda yet." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Use the "Add to meeting" button to add this work package to an upcoming meeting.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/cs.yml b/modules/meeting/config/locales/crowdin/cs.yml index 9b645ea3237..bb3c5bf12eb 100644 --- a/modules/meeting/config/locales/crowdin/cs.yml +++ b/modules/meeting/config/locales/crowdin/cs.yml @@ -625,7 +625,9 @@ cs: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "Tento pracovní balíček ještě není naplánován na nadcházející program schůzky." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Použijte tlačítko "Přidat do schůzky" pro přidání tohoto pracovního balíčku na nadcházející schůzku.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/da.yml b/modules/meeting/config/locales/crowdin/da.yml index 44642c44f4e..2cde9a2f555 100644 --- a/modules/meeting/config/locales/crowdin/da.yml +++ b/modules/meeting/config/locales/crowdin/da.yml @@ -611,7 +611,9 @@ da: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "This work package is not scheduled in an upcoming meeting agenda yet." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Use the "Add to meeting" button to add this work package to an upcoming meeting.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/de.yml b/modules/meeting/config/locales/crowdin/de.yml index 5590717b534..704b394b774 100644 --- a/modules/meeting/config/locales/crowdin/de.yml +++ b/modules/meeting/config/locales/crowdin/de.yml @@ -611,7 +611,9 @@ de: text_agenda_item_duplicate_in_next_meeting: "Sind Sie sicher, dass Sie eine Kopie dieses Tagesordnungspunktes in die nächste Besprechung am %{date} um %{time} aufnehmen wollen? Ergebnisse dieses Eintrags werden nicht kopiert." text_agenda_item_duplicated_in_next_meeting: "Tagesordnungspunkt zum nächsten Besprechung am %{date} kopiert" text_work_package_has_no_upcoming_meeting_agenda_items: "Dieses Arbeitspaket ist bisher in keiner anstehenden Besprechung enthalten." - text_agenda_item_move_next_meeting_cancelled: "Der Tagesordnungspunkt konnte nicht in die nächste Besprechung verschoben werden da diese abgesagt wurde." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Über den Button "Zur Besprechung hinzufügen" können Sie dieses Arbeitspaket zu einer zukünftigen Besprechung hinzuzufügen.' text_work_package_has_no_past_meeting_agenda_items: "Dieses Arbeitspaket wurde in einer früheren Besprechung nicht als Tagesordnungspunkt hinzugefügt." text_email_updates_muted: "E-Mail-Kalenderaktualisierungen sind deaktiviert. Die Teilnehmer erhalten keine aktualisierten Einladungen per E-Mail, wenn Sie Änderungen vornehmen." diff --git a/modules/meeting/config/locales/crowdin/el.yml b/modules/meeting/config/locales/crowdin/el.yml index 4578f8e471b..df194dac346 100644 --- a/modules/meeting/config/locales/crowdin/el.yml +++ b/modules/meeting/config/locales/crowdin/el.yml @@ -611,7 +611,9 @@ el: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "This work package is not scheduled in an upcoming meeting agenda yet." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Use the "Add to meeting" button to add this work package to an upcoming meeting.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/eo.yml b/modules/meeting/config/locales/crowdin/eo.yml index 4a49f8754ff..72002d4ccd0 100644 --- a/modules/meeting/config/locales/crowdin/eo.yml +++ b/modules/meeting/config/locales/crowdin/eo.yml @@ -611,7 +611,9 @@ eo: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "This work package is not scheduled in an upcoming meeting agenda yet." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Use the "Add to meeting" button to add this work package to an upcoming meeting.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/es.yml b/modules/meeting/config/locales/crowdin/es.yml index 880392f5198..0ed581d8349 100644 --- a/modules/meeting/config/locales/crowdin/es.yml +++ b/modules/meeting/config/locales/crowdin/es.yml @@ -611,7 +611,9 @@ es: text_agenda_item_duplicate_in_next_meeting: "¿Seguro que deseas añadir una copia de este punto del orden del día a la próxima reunión, el %{date} a las %{time}? Los resultados no se duplicarán." text_agenda_item_duplicated_in_next_meeting: "Punto del orden del día duplicado a la próxima reunión, el %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "Este paquete de trabajo no está todavía programado en ninguna agenda de próximas reuniones." - text_agenda_item_move_next_meeting_cancelled: "No se puede mover a la próxima reunión, ya que ha sido cancelada." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Utilice el botón "Añadir a la reunión" para añadir este paquete de trabajo a una próxima reunión.' text_work_package_has_no_past_meeting_agenda_items: "Este paquete de trabajo no se añadió como punto del orden del día en una reunión anterior." text_email_updates_muted: "Las actualizaciones de calendario por correo electrónico están silenciadas. Los participantes no recibirán invitaciones actualizadas por correo electrónico cuando realice cambios." diff --git a/modules/meeting/config/locales/crowdin/et.yml b/modules/meeting/config/locales/crowdin/et.yml index 41010d760c8..2376dc86a34 100644 --- a/modules/meeting/config/locales/crowdin/et.yml +++ b/modules/meeting/config/locales/crowdin/et.yml @@ -611,7 +611,9 @@ et: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "This work package is not scheduled in an upcoming meeting agenda yet." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Use the "Add to meeting" button to add this work package to an upcoming meeting.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/eu.yml b/modules/meeting/config/locales/crowdin/eu.yml index 52276c48d73..c4cff0d7d68 100644 --- a/modules/meeting/config/locales/crowdin/eu.yml +++ b/modules/meeting/config/locales/crowdin/eu.yml @@ -611,7 +611,9 @@ eu: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "This work package is not scheduled in an upcoming meeting agenda yet." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Use the "Add to meeting" button to add this work package to an upcoming meeting.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/fa.yml b/modules/meeting/config/locales/crowdin/fa.yml index f9f02c8f017..c76783791fe 100644 --- a/modules/meeting/config/locales/crowdin/fa.yml +++ b/modules/meeting/config/locales/crowdin/fa.yml @@ -611,7 +611,9 @@ fa: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "This work package is not scheduled in an upcoming meeting agenda yet." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Use the "Add to meeting" button to add this work package to an upcoming meeting.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/fi.yml b/modules/meeting/config/locales/crowdin/fi.yml index 4667d6e0e05..0427c54cfd1 100644 --- a/modules/meeting/config/locales/crowdin/fi.yml +++ b/modules/meeting/config/locales/crowdin/fi.yml @@ -611,7 +611,9 @@ fi: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "This work package is not scheduled in an upcoming meeting agenda yet." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Use the "Add to meeting" button to add this work package to an upcoming meeting.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/fil.yml b/modules/meeting/config/locales/crowdin/fil.yml index 85cc2265b11..a9e0fb67d17 100644 --- a/modules/meeting/config/locales/crowdin/fil.yml +++ b/modules/meeting/config/locales/crowdin/fil.yml @@ -611,7 +611,9 @@ fil: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "This work package is not scheduled in an upcoming meeting agenda yet." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Use the "Add to meeting" button to add this work package to an upcoming meeting.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/fr.yml b/modules/meeting/config/locales/crowdin/fr.yml index c437a87cd58..c28af9442bf 100644 --- a/modules/meeting/config/locales/crowdin/fr.yml +++ b/modules/meeting/config/locales/crowdin/fr.yml @@ -611,7 +611,9 @@ fr: text_agenda_item_duplicate_in_next_meeting: "Êtes-vous sûr de vouloir ajouter une copie de ce point de l'ordre du jour à la prochaine réunion, sur %{date} à %{time}? Les résultats ne seront pas dupliqués." text_agenda_item_duplicated_in_next_meeting: "L'ordre du jour a été déplacé vers la prochaine réunion, le %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "Ce lot de travaux n'est pas encore inclus dans l'ordre du jour d'une prochaine réunion." - text_agenda_item_move_next_meeting_cancelled: "Impossible de passer à la prochaine réunion, car elle a été annulée." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Utilisez le bouton "Ajouter à la réunion" pour ajouter ce lot de travaux à une réunion à venir.' text_work_package_has_no_past_meeting_agenda_items: "Ce work package n'a pas été ajouté comme point à l'ordre du jour lors d'une réunion précédente." text_email_updates_muted: "Les mises à jour du calendrier par e-mail sont désactivées. Les participants ne recevront pas d'invitations actualisées par e-mail lorsque vous effectuerez des modifications." diff --git a/modules/meeting/config/locales/crowdin/he.yml b/modules/meeting/config/locales/crowdin/he.yml index 0cef9448ab5..faa99e3c959 100644 --- a/modules/meeting/config/locales/crowdin/he.yml +++ b/modules/meeting/config/locales/crowdin/he.yml @@ -625,7 +625,9 @@ he: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "This work package is not scheduled in an upcoming meeting agenda yet." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Use the "Add to meeting" button to add this work package to an upcoming meeting.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/hi.yml b/modules/meeting/config/locales/crowdin/hi.yml index 307b1f5e95e..ef2240ed32b 100644 --- a/modules/meeting/config/locales/crowdin/hi.yml +++ b/modules/meeting/config/locales/crowdin/hi.yml @@ -611,7 +611,9 @@ hi: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "This work package is not scheduled in an upcoming meeting agenda yet." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Use the "Add to meeting" button to add this work package to an upcoming meeting.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/hr.yml b/modules/meeting/config/locales/crowdin/hr.yml index e1afc75cd21..c9b2cb968c2 100644 --- a/modules/meeting/config/locales/crowdin/hr.yml +++ b/modules/meeting/config/locales/crowdin/hr.yml @@ -618,7 +618,9 @@ hr: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "This work package is not scheduled in an upcoming meeting agenda yet." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Use the "Add to meeting" button to add this work package to an upcoming meeting.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/hu.yml b/modules/meeting/config/locales/crowdin/hu.yml index 51d7b113aad..3df5f82cf42 100644 --- a/modules/meeting/config/locales/crowdin/hu.yml +++ b/modules/meeting/config/locales/crowdin/hu.yml @@ -611,7 +611,9 @@ hu: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "Ez a munkacsomag még nem szerepel a következő megbeszélések napirendjén." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Használja a "Hozzáadás a megbeszéléshez" gombot, ha ezt a munkacsomagot hozzá kívánja adni egy közelgő értekezlethez.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/id.yml b/modules/meeting/config/locales/crowdin/id.yml index e5de7e95623..1ab9f57d444 100644 --- a/modules/meeting/config/locales/crowdin/id.yml +++ b/modules/meeting/config/locales/crowdin/id.yml @@ -604,7 +604,9 @@ id: text_agenda_item_duplicate_in_next_meeting: "Apakah Anda yakin ingin menambahkan salinan item agenda ini ke rapat berikutnya pada %{date} pukul %{time}? Hasil rapat tidak akan diduplikasi." text_agenda_item_duplicated_in_next_meeting: "Agenda item yang sama terulang dalam rapat berikutnya, yaitu pada %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "Paket kerja ini belum dijadwalkan dalam agenda rapat mendatang." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Gunakan tombol “Tambahkan ke rapat” untuk menambahkan paket kerja ini ke rapat yang akan datang.' text_work_package_has_no_past_meeting_agenda_items: "Paket kerja ini tidak ditambahkan sebagai agenda dalam rapat sebelumnya." text_email_updates_muted: "Pembaruan kalender melalui email telah dinonaktifkan. Peserta tidak akan menerima undangan yang diperbarui melalui email saat Anda melakukan perubahan." diff --git a/modules/meeting/config/locales/crowdin/it.yml b/modules/meeting/config/locales/crowdin/it.yml index ddaa3db7d87..523dc1299c7 100644 --- a/modules/meeting/config/locales/crowdin/it.yml +++ b/modules/meeting/config/locales/crowdin/it.yml @@ -611,7 +611,9 @@ it: text_agenda_item_duplicate_in_next_meeting: "Vuoi davvero aggiungere una copia di questo punto all'ordine del giorno della prossima riunione, il giorno %{date} alle ore %{time}? I risultati non verranno duplicati." text_agenda_item_duplicated_in_next_meeting: "Punto all'ordine del giorno duplicato nella prossima riunione, il giorno %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "Questa macro-attività non è ancora programmata in un prossimo ordine del giorno di riunione." - text_agenda_item_move_next_meeting_cancelled: "Impossibile passare alla riunione successiva perché è stata annullata." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Utilizzare il pulsante "Aggiungi alla riunione" per aggiungere questa macro-attività a una riunione imminente.' text_work_package_has_no_past_meeting_agenda_items: "Questa macro-attività non è stata aggiunta come punto all'ordine del giorno in una riunione precedente." text_email_updates_muted: "Gli aggiornamenti del calendario via email sono disattivati. I partecipanti non riceveranno inviti aggiornati via email quando apporti modifiche." diff --git a/modules/meeting/config/locales/crowdin/ja.yml b/modules/meeting/config/locales/crowdin/ja.yml index 9f48cce4bbb..8ad09ec8e47 100644 --- a/modules/meeting/config/locales/crowdin/ja.yml +++ b/modules/meeting/config/locales/crowdin/ja.yml @@ -604,7 +604,9 @@ ja: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "このワークパッケージは、まだ今後の会議の議題に予定されていません。" - text_agenda_item_move_next_meeting_cancelled: "次回の会議がキャンセルされたため、移動できない。" + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: '「会議に追加」ボタンを使用して、このワークパッケージを今後の会議に追加してください。' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "メールカレンダーの更新はミュートされます。変更を行った場合、参加者はメールで更新された招待状を受け取りません。" diff --git a/modules/meeting/config/locales/crowdin/ka.yml b/modules/meeting/config/locales/crowdin/ka.yml index 8e0bb5981b8..3b2f60ddc0a 100644 --- a/modules/meeting/config/locales/crowdin/ka.yml +++ b/modules/meeting/config/locales/crowdin/ka.yml @@ -611,7 +611,9 @@ ka: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "This work package is not scheduled in an upcoming meeting agenda yet." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Use the "Add to meeting" button to add this work package to an upcoming meeting.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/kk.yml b/modules/meeting/config/locales/crowdin/kk.yml index cb8d63634a7..0b58d3868c3 100644 --- a/modules/meeting/config/locales/crowdin/kk.yml +++ b/modules/meeting/config/locales/crowdin/kk.yml @@ -611,7 +611,9 @@ kk: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "This work package is not scheduled in an upcoming meeting agenda yet." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Use the "Add to meeting" button to add this work package to an upcoming meeting.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/ko.yml b/modules/meeting/config/locales/crowdin/ko.yml index 32f63fca6c1..3d43722b9e6 100644 --- a/modules/meeting/config/locales/crowdin/ko.yml +++ b/modules/meeting/config/locales/crowdin/ko.yml @@ -604,7 +604,9 @@ ko: text_agenda_item_duplicate_in_next_meeting: "%{date}, %{time}의 다음 미팅에 이 의제의 사본을 추가하시겠습니까? 결과는 복제되지 않습니다." text_agenda_item_duplicated_in_next_meeting: "의제 항목이 %{date}의 다음 미팅에서 복제되었습니다" text_work_package_has_no_upcoming_meeting_agenda_items: "이 작업 패키지는 향후 미팅 의제에서 아직 예약되지 않았습니다." - text_agenda_item_move_next_meeting_cancelled: "취소되었으므로 다음 미팅으로 이동할 수 없습니다." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: '이 작업 패키지를 향후 미팅에 추가하려면 "미팅에 추가" 버튼을 사용하세요.' text_work_package_has_no_past_meeting_agenda_items: "이 작업 패키지는 지난 미팅에서 의제 항목으로 추가되지 않았습니다." text_email_updates_muted: "이메일 캘린더 업데이트가 음소거되었습니다. 변경해도 이메일을 통해 업데이트된 초대장이 참가자에게 전송되지 않습니다." diff --git a/modules/meeting/config/locales/crowdin/lt.yml b/modules/meeting/config/locales/crowdin/lt.yml index cc9ba86d6a6..4ca5d5c8956 100644 --- a/modules/meeting/config/locales/crowdin/lt.yml +++ b/modules/meeting/config/locales/crowdin/lt.yml @@ -625,7 +625,9 @@ lt: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "Šis darbo paketas dar nesuplanuotas artėjančio susitikimo plane." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Naudokite mygtuką „Pridėti į susitikimą“, kad pridėtumėte šį darbo paketą į ateinantį susitikimą.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/lv.yml b/modules/meeting/config/locales/crowdin/lv.yml index da130a43ec7..cce0516e017 100644 --- a/modules/meeting/config/locales/crowdin/lv.yml +++ b/modules/meeting/config/locales/crowdin/lv.yml @@ -618,7 +618,9 @@ lv: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "This work package is not scheduled in an upcoming meeting agenda yet." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Use the "Add to meeting" button to add this work package to an upcoming meeting.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/mn.yml b/modules/meeting/config/locales/crowdin/mn.yml index d0b71544291..a2e49af97c4 100644 --- a/modules/meeting/config/locales/crowdin/mn.yml +++ b/modules/meeting/config/locales/crowdin/mn.yml @@ -611,7 +611,9 @@ mn: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "This work package is not scheduled in an upcoming meeting agenda yet." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Use the "Add to meeting" button to add this work package to an upcoming meeting.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/ms.yml b/modules/meeting/config/locales/crowdin/ms.yml index 220e1b2636d..a1654964660 100644 --- a/modules/meeting/config/locales/crowdin/ms.yml +++ b/modules/meeting/config/locales/crowdin/ms.yml @@ -604,7 +604,9 @@ ms: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "Pakej kerja ini belum lagi dijadualkan dalam agenda mesyuarat akan datang." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Guna butang "Tambah ke mesyuarat" untuk tambah pakej kerja ini ke mesyuarat akan datang.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/ne.yml b/modules/meeting/config/locales/crowdin/ne.yml index 10daa567755..84510f91c49 100644 --- a/modules/meeting/config/locales/crowdin/ne.yml +++ b/modules/meeting/config/locales/crowdin/ne.yml @@ -611,7 +611,9 @@ ne: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "This work package is not scheduled in an upcoming meeting agenda yet." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Use the "Add to meeting" button to add this work package to an upcoming meeting.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/nl.yml b/modules/meeting/config/locales/crowdin/nl.yml index 2725736828b..dcd0ac12f42 100644 --- a/modules/meeting/config/locales/crowdin/nl.yml +++ b/modules/meeting/config/locales/crowdin/nl.yml @@ -611,7 +611,9 @@ nl: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "Dit werkpakket staat nog niet op de agenda van een komende vergadering." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Gebruik de knop "Aan vergadering toevoegen" om dit werkpakket aan een komende vergadering toe te voegen.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/no.yml b/modules/meeting/config/locales/crowdin/no.yml index 98c3c8ab4a0..fb0345de9f8 100644 --- a/modules/meeting/config/locales/crowdin/no.yml +++ b/modules/meeting/config/locales/crowdin/no.yml @@ -611,7 +611,9 @@ text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "Denne arbeidspakken er ikke planlagt i en kommende møteagenda ennå." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Bruk knappen "Legg til i møte" for å legge til denne arbeidspakken til et kommende møte.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/pl.yml b/modules/meeting/config/locales/crowdin/pl.yml index a84eec191f3..b545ec89321 100644 --- a/modules/meeting/config/locales/crowdin/pl.yml +++ b/modules/meeting/config/locales/crowdin/pl.yml @@ -625,7 +625,9 @@ pl: text_agenda_item_duplicate_in_next_meeting: "Czy na pewno chcesz dodać kopię tego punktu planu spotkania do następnego spotkania w dniu %{date} o godzinie %{time}? Wyniki nie będą duplikowane." text_agenda_item_duplicated_in_next_meeting: "Pozycję planu spotkania zduplikowano w następnym spotkaniu w dniu %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "Ten pakiet roboczy nie został jeszcze zaplanowany w planie nadchodzącego spotkania." - text_agenda_item_move_next_meeting_cancelled: "Nie można przenieść na następne spotkanie, ponieważ zostało ono anulowane." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Użyj przycisku „Dodaj do spotkania”, aby dodać ten pakiet roboczy do nadchodzącego spotkania.' text_work_package_has_no_past_meeting_agenda_items: "Ten pakiet roboczy nie został dodany jako pozycja planu spotkania w poprzednim spotkaniu." text_email_updates_muted: "Wysyłanie aktualizacji kalendarza pocztą elektroniczną jest wyciszone. Po wprowadzeniu zmian uczestnicy nie będą otrzymywać zaktualizowanych zaproszeń pocztą elektroniczną." diff --git a/modules/meeting/config/locales/crowdin/pt-BR.yml b/modules/meeting/config/locales/crowdin/pt-BR.yml index 8450e9eb319..5ee7d463497 100644 --- a/modules/meeting/config/locales/crowdin/pt-BR.yml +++ b/modules/meeting/config/locales/crowdin/pt-BR.yml @@ -611,7 +611,9 @@ pt-BR: text_agenda_item_duplicate_in_next_meeting: "Tem certeza de que deseja adicionar uma cópia deste item da agenda à próxima reunião, em %{date} às %{time}? Os resultados não serão duplicados." text_agenda_item_duplicated_in_next_meeting: "Item da agenda duplicado na próxima reunião, em %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "Este pacote de trabalho ainda não está programado na agenda de uma reunião futura." - text_agenda_item_move_next_meeting_cancelled: "Não é possível mover para a próxima reunião porque ela foi cancelada." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Use o botão "Adicionar à reunião" para adicionar este pacote de trabalho a uma próxima reunião.' text_work_package_has_no_past_meeting_agenda_items: "Este pacote de trabalho não foi adicionado como item da agenda em uma reunião anterior." text_email_updates_muted: "As atualizações de calendário por e-mail estão silenciadas. Os participantes não receberão convites atualizados por e-mail quando você fizer alterações." diff --git a/modules/meeting/config/locales/crowdin/pt-PT.yml b/modules/meeting/config/locales/crowdin/pt-PT.yml index d76236895a2..4631072b60a 100644 --- a/modules/meeting/config/locales/crowdin/pt-PT.yml +++ b/modules/meeting/config/locales/crowdin/pt-PT.yml @@ -611,7 +611,9 @@ pt-PT: text_agenda_item_duplicate_in_next_meeting: "Tem a certeza de que quer acrescentar uma cópia deste ponto da ordem de trabalhos à próxima reunião, em %{date} em %{time}? Os resultados não serão duplicados." text_agenda_item_duplicated_in_next_meeting: "Ponto da ordem de trabalhos duplicado na próxima reunião, em %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "Este pacote de trabalho ainda não está agendado em uma reunião futura." - text_agenda_item_move_next_meeting_cancelled: "Não é possível mover para a próxima reunião uma vez que foi cancelada." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Use o botão "Adicionar à reunião" para adicionar este pacote de trabalho a uma reunião futura.' text_work_package_has_no_past_meeting_agenda_items: "Este pacote de trabalho não foi acrescentado como ponto da ordem de trabalhos numa reunião anterior." text_email_updates_muted: "As atualizações do calendário por e-mail estão silenciadas. Os participantes não receberão convites atualizados por e-mail quando fizer alterações." diff --git a/modules/meeting/config/locales/crowdin/ro.yml b/modules/meeting/config/locales/crowdin/ro.yml index 121668db911..79652279135 100644 --- a/modules/meeting/config/locales/crowdin/ro.yml +++ b/modules/meeting/config/locales/crowdin/ro.yml @@ -618,7 +618,9 @@ ro: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "This work package is not scheduled in an upcoming meeting agenda yet." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Use the "Add to meeting" button to add this work package to an upcoming meeting.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/ru.yml b/modules/meeting/config/locales/crowdin/ru.yml index f407c89927c..dae8719f7fa 100644 --- a/modules/meeting/config/locales/crowdin/ru.yml +++ b/modules/meeting/config/locales/crowdin/ru.yml @@ -625,7 +625,9 @@ ru: text_agenda_item_duplicate_in_next_meeting: "Вы уверены, что хотите добавить копию этого пункта повестки дня к следующему совещанию на %{date} в %{time}? Результаты не будут дублироваться." text_agenda_item_duplicated_in_next_meeting: "Пункт повестки дня дублируется на следующем совещании %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "Этот пакет работ пока не включен в повестку предстоящего совещания." - text_agenda_item_move_next_meeting_cancelled: "Не удалось переместить на следующее совещание, так как оно было отменено." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Используйте кнопку "Добавить к совещанию", чтобы добавить этот пакет работ к предстоящему совещанию.' text_work_package_has_no_past_meeting_agenda_items: "Этот рабочий пакет не был добавлен в качестве пункта повестки дня на прошлом совещании." text_email_updates_muted: "Обновления календаря по электронной почте отключены. Участники не будут получать обновленные приглашения по электронной почте, когда Вы вносите изменения." diff --git a/modules/meeting/config/locales/crowdin/rw.yml b/modules/meeting/config/locales/crowdin/rw.yml index e7b245f4302..d12a3985433 100644 --- a/modules/meeting/config/locales/crowdin/rw.yml +++ b/modules/meeting/config/locales/crowdin/rw.yml @@ -611,7 +611,9 @@ rw: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "This work package is not scheduled in an upcoming meeting agenda yet." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Use the "Add to meeting" button to add this work package to an upcoming meeting.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/si.yml b/modules/meeting/config/locales/crowdin/si.yml index 4674bd2ded9..4342a66a01d 100644 --- a/modules/meeting/config/locales/crowdin/si.yml +++ b/modules/meeting/config/locales/crowdin/si.yml @@ -611,7 +611,9 @@ si: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "This work package is not scheduled in an upcoming meeting agenda yet." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Use the "Add to meeting" button to add this work package to an upcoming meeting.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/sk.yml b/modules/meeting/config/locales/crowdin/sk.yml index e7be54a0a43..428c64853e6 100644 --- a/modules/meeting/config/locales/crowdin/sk.yml +++ b/modules/meeting/config/locales/crowdin/sk.yml @@ -625,7 +625,9 @@ sk: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "This work package is not scheduled in an upcoming meeting agenda yet." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Use the "Add to meeting" button to add this work package to an upcoming meeting.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/sl.yml b/modules/meeting/config/locales/crowdin/sl.yml index 6d1b95647c3..dd7c519509f 100644 --- a/modules/meeting/config/locales/crowdin/sl.yml +++ b/modules/meeting/config/locales/crowdin/sl.yml @@ -625,7 +625,9 @@ sl: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "This work package is not scheduled in an upcoming meeting agenda yet." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Use the "Add to meeting" button to add this work package to an upcoming meeting.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/sr.yml b/modules/meeting/config/locales/crowdin/sr.yml index 61c436dde75..3390ff54d72 100644 --- a/modules/meeting/config/locales/crowdin/sr.yml +++ b/modules/meeting/config/locales/crowdin/sr.yml @@ -618,7 +618,9 @@ sr: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "This work package is not scheduled in an upcoming meeting agenda yet." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Use the "Add to meeting" button to add this work package to an upcoming meeting.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/sv.yml b/modules/meeting/config/locales/crowdin/sv.yml index ba943afbdea..fd4385237e7 100644 --- a/modules/meeting/config/locales/crowdin/sv.yml +++ b/modules/meeting/config/locales/crowdin/sv.yml @@ -611,7 +611,9 @@ sv: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "Detta arbetspaket är ännu inte inplanerat i en kommande dagordning." - text_agenda_item_move_next_meeting_cancelled: "Det går inte att gå vidare till nästa möte eftersom det har ställts in." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Använd knappen "Lägg till i möte" för att lägga till detta arbetspaket i ett kommande möte.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Kalenderuppdateringar via e-post är avstängda. Deltagarna kommer inte att få uppdaterade inbjudningar via e-post när du gör ändringar." diff --git a/modules/meeting/config/locales/crowdin/th.yml b/modules/meeting/config/locales/crowdin/th.yml index 6657290f281..4500e469b3b 100644 --- a/modules/meeting/config/locales/crowdin/th.yml +++ b/modules/meeting/config/locales/crowdin/th.yml @@ -604,7 +604,9 @@ th: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "This work package is not scheduled in an upcoming meeting agenda yet." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Use the "Add to meeting" button to add this work package to an upcoming meeting.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/tr.yml b/modules/meeting/config/locales/crowdin/tr.yml index 40f3a2877f5..857f0546eb9 100644 --- a/modules/meeting/config/locales/crowdin/tr.yml +++ b/modules/meeting/config/locales/crowdin/tr.yml @@ -611,7 +611,9 @@ tr: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "Bu iş paketi henüz yaklaşan bir toplantı gündemine alınmamıştır." - text_agenda_item_move_next_meeting_cancelled: "Toplantı iptal edildiği için sonraki toplantıya taşınamadı." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Bu iş paketini yaklaşan bir toplantıya eklemek için "Toplantıya ekle" düğmesini kullanın.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/uk.yml b/modules/meeting/config/locales/crowdin/uk.yml index 306cc4fae43..c091e326e3f 100644 --- a/modules/meeting/config/locales/crowdin/uk.yml +++ b/modules/meeting/config/locales/crowdin/uk.yml @@ -625,7 +625,9 @@ uk: text_agenda_item_duplicate_in_next_meeting: "Справді додати копію цього пункту порядку денного в наступну нараду, що відбудеться %{date} о %{time}? Результати не дублюватимуться." text_agenda_item_duplicated_in_next_meeting: "Пункт порядку денного продубльовано в нову нараду, що відбудеться %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "Цей пакет робіт ще не заплановано в порядку денному майбутньої наради." - text_agenda_item_move_next_meeting_cancelled: "Не вдається перейти до наступної наради, оскільки її скасовано." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Натисніть кнопку «Додати до наради», щоб додати цей пакет робіт до майбутньої наради.' text_work_package_has_no_past_meeting_agenda_items: "Цей пакет робіт не було додано як пункт порядку денного в минулу нараду." text_email_updates_muted: "Надсилання електронною поштою оновлень із календаря тимчасово вимкнено. Учасники не отримають листи з оновленими запрошеннями після того, як ви внесете зміни." diff --git a/modules/meeting/config/locales/crowdin/uz.yml b/modules/meeting/config/locales/crowdin/uz.yml index 27c7f31c74b..34bb84e64d2 100644 --- a/modules/meeting/config/locales/crowdin/uz.yml +++ b/modules/meeting/config/locales/crowdin/uz.yml @@ -611,7 +611,9 @@ uz: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "This work package is not scheduled in an upcoming meeting agenda yet." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Use the "Add to meeting" button to add this work package to an upcoming meeting.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/vi.yml b/modules/meeting/config/locales/crowdin/vi.yml index e6824bf51e7..9eabce7a5fc 100644 --- a/modules/meeting/config/locales/crowdin/vi.yml +++ b/modules/meeting/config/locales/crowdin/vi.yml @@ -604,7 +604,9 @@ vi: text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "Gói công việc này chưa được lên lịch trong chương trình nghị sự cuộc họp sắp tới." - text_agenda_item_move_next_meeting_cancelled: "Unable to move to the next meeting since it has been cancelled." + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: 'Sử dụng nút "Thêm vào cuộc họp" để thêm gói công việc này vào một cuộc họp sắp tới.' text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." diff --git a/modules/meeting/config/locales/crowdin/zh-CN.yml b/modules/meeting/config/locales/crowdin/zh-CN.yml index 3e35f9dbbb4..d95e32a51e3 100644 --- a/modules/meeting/config/locales/crowdin/zh-CN.yml +++ b/modules/meeting/config/locales/crowdin/zh-CN.yml @@ -604,7 +604,9 @@ zh-CN: text_agenda_item_duplicate_in_next_meeting: "确定要将此议程条目的副本添加到 %{date} %{time} 的下一个会议吗?结果不会复制。" text_agenda_item_duplicated_in_next_meeting: "在 %{date}的下一个会议中复制的议程条目" text_work_package_has_no_upcoming_meeting_agenda_items: "该工作包尚未被安排到即将举行的会议议程中。" - text_agenda_item_move_next_meeting_cancelled: "无法移动到下次会议,因为它已被取消。" + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: '使用"添加到会议"按钮将此工作包添加到即将举行的会议。' text_work_package_has_no_past_meeting_agenda_items: "此工作包未作为过往会议中的议程条目添加。" text_email_updates_muted: "电子邮件日历更新已静音。当您进行更改时,与会者将不会通过电子邮件收到更新的邀请。" diff --git a/modules/meeting/config/locales/crowdin/zh-TW.yml b/modules/meeting/config/locales/crowdin/zh-TW.yml index 4cde114d109..d1ecac26df1 100644 --- a/modules/meeting/config/locales/crowdin/zh-TW.yml +++ b/modules/meeting/config/locales/crowdin/zh-TW.yml @@ -604,7 +604,9 @@ zh-TW: text_agenda_item_duplicate_in_next_meeting: "您確定要在下次會議中加入此議程項目的副本, %{date} ,網址為 %{time}? 結果不會重複。" text_agenda_item_duplicated_in_next_meeting: "與下次會議重複的議程項目, %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "該工作套件尚未被安排到即將舉行的會議議程中。" - text_agenda_item_move_next_meeting_cancelled: "由於下次會議已被取消,因此無法轉到下次會議。" + text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." + text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." text_work_package_add_to_meeting_hint: '使用"增加到會議"按鈕將此工作套件新增到即將舉行的會議。' text_work_package_has_no_past_meeting_agenda_items: "此工作套件未被新增為過去會議中的議程項目。" text_email_updates_muted: "將停止電子郵件通知「行事曆更新」。當您變更時,參與者將不會透過電子郵件收到更新的邀請。" From 1e3c596c0cff1b4082423be4a0dd978ae74b7c80 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 05:36:24 +0000 Subject: [PATCH 060/293] Bump @stylistic/eslint-plugin from 5.7.0 to 5.7.1 in /frontend Bumps [@stylistic/eslint-plugin](https://github.com/eslint-stylistic/eslint-stylistic/tree/HEAD/packages/eslint-plugin) from 5.7.0 to 5.7.1. - [Release notes](https://github.com/eslint-stylistic/eslint-stylistic/releases) - [Changelog](https://github.com/eslint-stylistic/eslint-stylistic/blob/main/CHANGELOG.md) - [Commits](https://github.com/eslint-stylistic/eslint-stylistic/commits/v5.7.1/packages/eslint-plugin) --- updated-dependencies: - dependency-name: "@stylistic/eslint-plugin" dependency-version: 5.7.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 153 +++++++++---------------------------- frontend/package.json | 2 +- 2 files changed, 37 insertions(+), 118 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 182c1422e8a..4dc0f2c2dbd 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -140,7 +140,7 @@ "@html-eslint/eslint-plugin": "^0.54.0", "@html-eslint/parser": "^0.54.0", "@jsdevtools/coverage-istanbul-loader": "3.0.5", - "@stylistic/eslint-plugin": "^5.6.1", + "@stylistic/eslint-plugin": "^5.7.1", "@types/codemirror": "5.60.5", "@types/dom-navigation": "^1.0.3", "@types/dragula": "^3.7.5", @@ -4757,37 +4757,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@eslint/eslintrc/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/@eslint/eslintrc/node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -8730,16 +8699,15 @@ } }, "node_modules/@stylistic/eslint-plugin": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.7.0.tgz", - "integrity": "sha512-PsSugIf9ip1H/mWKj4bi/BlEoerxXAda9ByRFsYuwsmr6af9NxJL0AaiNXs8Le7R21QR5KMiD/KdxZZ71LjAxQ==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.7.1.tgz", + "integrity": "sha512-zjTUwIsEfT+k9BmXwq1QEFYsb4afBlsI1AXFyWQBgggMzwBFOuu92pGrE5OFx90IOjNl+lUbQoTG7f8S0PkOdg==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/types": "^8.52.0", - "eslint-visitor-keys": "^5.0.0", - "espree": "^11.0.0", + "@typescript-eslint/types": "^8.53.1", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "estraverse": "^5.3.0", "picomatch": "^4.0.3" }, @@ -8751,13 +8719,12 @@ } }, "node_modules/@stylistic/eslint-plugin/node_modules/eslint-visitor-keys": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.0.tgz", - "integrity": "sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, - "license": "Apache-2.0", "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -14349,24 +14316,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/eslint/node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -14414,31 +14363,29 @@ } }, "node_modules/espree": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-11.0.0.tgz", - "integrity": "sha512-+gMeWRrIh/NsG+3NaLeWHuyeyk70p2tbvZIWBYcqQ4/7Xvars6GYTZNhF1sIeLcc6Wb11He5ffz3hsHyXFrw5A==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^5.0.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.0.tgz", - "integrity": "sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, - "license": "Apache-2.0", "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -28604,23 +28551,6 @@ "uri-js": "^4.2.2" } }, - "eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true - }, - "espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "requires": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - } - }, "globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -30953,23 +30883,23 @@ "integrity": "sha512-0ShvvDiG4qNLyFUTDrjGiR9MWR6D9EiAJRUSKxTPHA5Cc2Ci/A4Qj7cHDCoK2ZGHhpESfK0LsR9xtySCN6FTQw==" }, "@stylistic/eslint-plugin": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.7.0.tgz", - "integrity": "sha512-PsSugIf9ip1H/mWKj4bi/BlEoerxXAda9ByRFsYuwsmr6af9NxJL0AaiNXs8Le7R21QR5KMiD/KdxZZ71LjAxQ==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.7.1.tgz", + "integrity": "sha512-zjTUwIsEfT+k9BmXwq1QEFYsb4afBlsI1AXFyWQBgggMzwBFOuu92pGrE5OFx90IOjNl+lUbQoTG7f8S0PkOdg==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/types": "^8.52.0", - "eslint-visitor-keys": "^5.0.0", - "espree": "^11.0.0", + "@typescript-eslint/types": "^8.53.1", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "estraverse": "^5.3.0", "picomatch": "^4.0.3" }, "dependencies": { "eslint-visitor-keys": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.0.tgz", - "integrity": "sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true }, "picomatch": { @@ -34631,17 +34561,6 @@ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true }, - "espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "requires": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - } - }, "glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -34910,20 +34829,20 @@ } }, "espree": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-11.0.0.tgz", - "integrity": "sha512-+gMeWRrIh/NsG+3NaLeWHuyeyk70p2tbvZIWBYcqQ4/7Xvars6GYTZNhF1sIeLcc6Wb11He5ffz3hsHyXFrw5A==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "requires": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^5.0.0" + "eslint-visitor-keys": "^4.2.1" }, "dependencies": { "eslint-visitor-keys": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.0.tgz", - "integrity": "sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true } } diff --git a/frontend/package.json b/frontend/package.json index 8edea1ea874..18a4f4d79f6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,7 +17,7 @@ "@html-eslint/eslint-plugin": "^0.54.0", "@html-eslint/parser": "^0.54.0", "@jsdevtools/coverage-istanbul-loader": "3.0.5", - "@stylistic/eslint-plugin": "^5.6.1", + "@stylistic/eslint-plugin": "^5.7.1", "@types/codemirror": "5.60.5", "@types/dom-navigation": "^1.0.3", "@types/dragula": "^3.7.5", From 7236e78c6161121619ae651b24049420b6c96197 Mon Sep 17 00:00:00 2001 From: ulferts Date: Wed, 4 Feb 2026 09:23:30 +0100 Subject: [PATCH 061/293] no longer validate work package itself on wp activity update This is in line with changes made regarding e.g. custom fields --- .../work_packages/create_note_contract.rb | 2 ++ .../work_packages/create_note_contract_spec.rb | 14 +++++++++++++- .../v3/activities_by_work_package_resource_spec.rb | 14 ++++---------- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/app/contracts/work_packages/create_note_contract.rb b/app/contracts/work_packages/create_note_contract.rb index 937cc1030c0..a99e6b74854 100644 --- a/app/contracts/work_packages/create_note_contract.rb +++ b/app/contracts/work_packages/create_note_contract.rb @@ -32,6 +32,8 @@ module WorkPackages class CreateNoteContract < ::ModelContract def self.model = WorkPackage + def validate_model? = false + attribute :journal_notes do errors.add(:journal_notes, :error_unauthorized) unless adding_notes_allowed? errors.add(:journal_notes, :blank) if model.journal_notes.blank? diff --git a/spec/contracts/work_packages/create_note_contract_spec.rb b/spec/contracts/work_packages/create_note_contract_spec.rb index 14c9d7a5b84..5366b0d2600 100644 --- a/spec/contracts/work_packages/create_note_contract_spec.rb +++ b/spec/contracts/work_packages/create_note_contract_spec.rb @@ -40,7 +40,6 @@ RSpec.describe WorkPackages::CreateNoteContract do # we need to clear the changes information because otherwise the # contract will complain about all the changes to read_only attributes wp.send(:clear_changes_information) - allow(wp).to receive(:valid?).and_return true wp end @@ -151,5 +150,18 @@ RSpec.describe WorkPackages::CreateNoteContract do it_behaves_like "contract is invalid", subject: :error_readonly end + + describe "with the work package already being invalid" do + before do + work_package.done_ratio = -100 + + # Otherwise, the contract would complain about changing a read-only attribute + work_package.send(:clear_changes_information) + + work_package.journal_notes = "abc" + end + + it_behaves_like "contract is valid" + end end end diff --git a/spec/requests/api/v3/activities_by_work_package_resource_spec.rb b/spec/requests/api/v3/activities_by_work_package_resource_spec.rb index 8dc18ab93dd..98a84678b87 100644 --- a/spec/requests/api/v3/activities_by_work_package_resource_spec.rb +++ b/spec/requests/api/v3/activities_by_work_package_resource_spec.rb @@ -132,20 +132,14 @@ RSpec.describe API::V3::Activities::ActivitiesByWorkPackageAPI, with_ee: [:inter context "with an erroneous work package" do before do - work_package.subject = "" + work_package.done_ratio = -100 work_package.save!(validate: false) end - include_context "create activity" + it_behaves_like "valid activity request" do + let(:status_code) { 201 } - it "responds with error" do - expect(last_response).to have_http_status :unprocessable_entity - end - - it "notes the error" do - expect(last_response.body) - .to be_json_eql("Subject can't be blank.".to_json) - .at_path("message") + include_context "create activity" end end From 1d39cb48b0f846a39508e8f8a2ba20e8cdedf022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tizian=20R=C3=B6=C3=9Fler?= Date: Wed, 4 Feb 2026 09:48:51 +0100 Subject: [PATCH 062/293] change Hocuspocus port mapping and update Apache WebSocket proxy docs for more clarity --- docs/system-admin-guide/documents/README.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/docs/system-admin-guide/documents/README.md b/docs/system-admin-guide/documents/README.md index 027c124bc9d..70ceffcc492 100644 --- a/docs/system-admin-guide/documents/README.md +++ b/docs/system-admin-guide/documents/README.md @@ -101,7 +101,7 @@ services: environment: SECRET: "secret123" ports: - - "127.0.0.1:8080:1234" + - "127.0.0.1:1234:1234" ``` Replace the `` with the image from [here](https://github.com/opf/openproject-docker-compose/blob/stable/17/docker-compose.yml#L122). @@ -119,9 +119,10 @@ docker compose up -d Create `/etc/openproject/addons/apache2/custom/vhost/hocuspocus.conf` with the following content: ```apache -ProxyPass /hocuspocus ws://127.0.0.1:8080/hocuspocus -ProxyPassReverse /hocuspocus ws://127.0.0.1:8080/hocuspocus +ProxyPass /hocuspocus ws://127.0.0.1:1234/hocuspocus +ProxyPassReverse /hocuspocus ws://127.0.0.1:1234/hocuspocus ``` +*For Debian/Ubuntu-based systems, run the following commands:* Enable the `proxy_wstunnel` module: @@ -135,10 +136,18 @@ Restart Apache: sudo service apache2 restart ``` +*For RHEL/CentOS-based systems, run the following command:* + +```shell +sudo service httpd restart +``` #### 3. Enable real-time collaboration -Manually configure the server URL & secret in the *Documents* administration settings in OpenProject. +Manually configure the server URL & secret in the *Documents* administration settings in OpenProject. +Here you need to provide the URL in the following format: `wss:///hocuspocus`. +If you are using HTTP in your instance, the protocol has to be `ws://` instead of `wss://`. + > [!NOTE] > The secret must be identical in both op-blocknote-hocuspocus and OpenProject. From 88d5b59ccd0c40eda6e279758e1a94000a82851e Mon Sep 17 00:00:00 2001 From: ulferts Date: Wed, 4 Feb 2026 09:51:44 +0100 Subject: [PATCH 063/293] adapt the WPCopyService so that auto generated attributes get copied as well Only to be overwritten later --- app/services/work_packages/copy_service.rb | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/app/services/work_packages/copy_service.rb b/app/services/work_packages/copy_service.rb index 8331c8588b0..eca67edaaf9 100644 --- a/app/services/work_packages/copy_service.rb +++ b/app/services/work_packages/copy_service.rb @@ -83,7 +83,7 @@ class WorkPackages::CopyService < BaseServices::BaseCallable attributes = work_package .attributes - .slice(*writable_work_package_attributes(work_package)) + .slice(*copyable_work_package_attributes(work_package)) .merge("custom_field_values" => work_package.custom_value_attributes) .merge(overwritten_attributes) @@ -96,8 +96,17 @@ class WorkPackages::CopyService < BaseServices::BaseCallable attributes end - def writable_work_package_attributes(work_package) - instantiate_contract(work_package, user).writable_attributes + # Returns attributes that should be copied from the source work package. + # This includes writable attributes plus any auto-generated attributes + # (which will be regenerated after saving) + def copyable_work_package_attributes(work_package) + contract = instantiate_contract(work_package, user) + writable = contract.writable_attributes + + # Include auto-generated attributes so they get set (to empty) and regenerated after save + auto_generated = work_package.type.enabled_patterns.keys.map(&:to_s) || [] + + (writable + auto_generated).uniq end def remove_author_watcher(copied) From b1c5838f476340ad824e413673d9196e621ba0d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tizian=20R=C3=B6=C3=9Fler?= Date: Wed, 4 Feb 2026 09:52:26 +0100 Subject: [PATCH 064/293] make text bold instead of cursive --- docs/system-admin-guide/documents/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/system-admin-guide/documents/README.md b/docs/system-admin-guide/documents/README.md index 70ceffcc492..5506fab3d7f 100644 --- a/docs/system-admin-guide/documents/README.md +++ b/docs/system-admin-guide/documents/README.md @@ -122,7 +122,7 @@ Create `/etc/openproject/addons/apache2/custom/vhost/hocuspocus.conf` with the f ProxyPass /hocuspocus ws://127.0.0.1:1234/hocuspocus ProxyPassReverse /hocuspocus ws://127.0.0.1:1234/hocuspocus ``` -*For Debian/Ubuntu-based systems, run the following commands:* +**For Debian/Ubuntu-based systems, run the following commands:** Enable the `proxy_wstunnel` module: @@ -136,7 +136,7 @@ Restart Apache: sudo service apache2 restart ``` -*For RHEL/CentOS-based systems, run the following command:* +**For RHEL/CentOS-based systems, run the following command:** ```shell sudo service httpd restart From e3068116ba58286fc6d0fad5dec5c180662a74b7 Mon Sep 17 00:00:00 2001 From: ulferts Date: Wed, 4 Feb 2026 10:46:11 +0100 Subject: [PATCH 065/293] rework copy contract to always allow setting subject --- app/contracts/work_packages/copy_contract.rb | 5 ++++ .../copy_service_integration_spec.rb | 23 ++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/app/contracts/work_packages/copy_contract.rb b/app/contracts/work_packages/copy_contract.rb index da6de498dd7..b22787f55a3 100644 --- a/app/contracts/work_packages/copy_contract.rb +++ b/app/contracts/work_packages/copy_contract.rb @@ -37,6 +37,11 @@ module WorkPackages attribute :done_ratio, writable: true + # Subject must always be writable during copy even when the type auto-generates it. + # Setting the value is ok because it will be regenerated after saving. + attribute :subject, + writable: true + # Use the default permission for the create contract, which is :add_work_packages. attribute_permission :project_phase_definition_id, :add_work_packages diff --git a/spec/services/work_packages/copy_service_integration_spec.rb b/spec/services/work_packages/copy_service_integration_spec.rb index af70f77d88c..bacd9758074 100644 --- a/spec/services/work_packages/copy_service_integration_spec.rb +++ b/spec/services/work_packages/copy_service_integration_spec.rb @@ -49,7 +49,7 @@ RSpec.describe WorkPackages::CopyService, "integration", type: :model do end shared_let(:project_phase_definition) { create(:project_phase_definition) } - shared_let(:work_package) do + shared_let(:work_package, reload: true) do create(:work_package, author: user, project:, type:, project_phase_definition:) end @@ -387,5 +387,26 @@ RSpec.describe WorkPackages::CopyService, "integration", type: :model do end end end + + context "with a type auto-generating subjects" do + let(:type_with_pattern) do + create(:type, patterns: { subject: { blueprint: "{{type}} {{id}} {{project_name}}", enabled: true } }) do |type| + project.types << type + end + end + + before do + work_package.update!(type: type_with_pattern) + end + + it "is success" do + expect(service_result) + .to be_success + end + + it "sets the auto generated subject" do + expect(copy.subject).to eq("#{type_with_pattern.name} #{copy.id} #{project.name}") + end + end end end From cc9155445a922f75111e6afc537b9d32451cd310 Mon Sep 17 00:00:00 2001 From: ulferts Date: Wed, 4 Feb 2026 13:01:20 +0100 Subject: [PATCH 066/293] generalize generated attribute read-only safeguard --- app/contracts/work_packages/base_contract.rb | 21 ++++++++++++++++---- app/contracts/work_packages/copy_contract.rb | 12 ++++++----- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/app/contracts/work_packages/base_contract.rb b/app/contracts/work_packages/base_contract.rb index 17599903f5c..e7107492767 100644 --- a/app/contracts/work_packages/base_contract.rb +++ b/app/contracts/work_packages/base_contract.rb @@ -34,10 +34,7 @@ module WorkPackages include AssignableValuesContract include WorkPackages::SetAttributesService::ProgressValuesCalculations - attribute :subject, - writable: ->(*) { - !model.type&.replacement_pattern_defined_for?(:subject) - } + attribute :subject attribute :description attribute :status_id, permission: %i[edit_work_packages change_work_package_status], @@ -247,6 +244,16 @@ module WorkPackages def valid?(context = :saving_custom_fields) = super + def writable_attributes + attributes = super + + unless auto_generated_attributes_writable? + attributes -= auto_generated_attribute_names + end + + attributes + end + private def validate_after_soonest_start(date_attribute) @@ -688,5 +695,11 @@ module WorkPackages def leaf_or_manually_scheduled? model.leaf? || model.schedule_manually? end + + def auto_generated_attributes_writable? = false + + def auto_generated_attribute_names + (model.type && model.type.enabled_patterns&.keys&.map(&:to_s)) || [] + end end end diff --git a/app/contracts/work_packages/copy_contract.rb b/app/contracts/work_packages/copy_contract.rb index b22787f55a3..054c1985a34 100644 --- a/app/contracts/work_packages/copy_contract.rb +++ b/app/contracts/work_packages/copy_contract.rb @@ -37,11 +37,6 @@ module WorkPackages attribute :done_ratio, writable: true - # Subject must always be writable during copy even when the type auto-generates it. - # Setting the value is ok because it will be regenerated after saving. - attribute :subject, - writable: true - # Use the default permission for the create contract, which is :add_work_packages. attribute_permission :project_phase_definition_id, :add_work_packages @@ -56,5 +51,12 @@ module WorkPackages # might not be active in the project yet. But when it is activated later, # the value should then be present. def validate_phase_active_in_project; end + + private + + # Auto-generated attributes are ok to be writable. The input does not come from the + # user so there is no need to run into a "read only error". + # The actual values will be regenerated after saving. + def auto_generated_attributes_writable? = true end end From 959e7d75f394ad0abd70c6f9452a8adf7bf1daa0 Mon Sep 17 00:00:00 2001 From: ulferts Date: Wed, 4 Feb 2026 13:09:22 +0100 Subject: [PATCH 067/293] resimplify copy service by relying on the contracts to provide the correct writable attributes --- app/services/work_packages/copy_service.rb | 15 +++------------ .../work_package_schema_representer_spec.rb | 5 ++--- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/app/services/work_packages/copy_service.rb b/app/services/work_packages/copy_service.rb index eca67edaaf9..8331c8588b0 100644 --- a/app/services/work_packages/copy_service.rb +++ b/app/services/work_packages/copy_service.rb @@ -83,7 +83,7 @@ class WorkPackages::CopyService < BaseServices::BaseCallable attributes = work_package .attributes - .slice(*copyable_work_package_attributes(work_package)) + .slice(*writable_work_package_attributes(work_package)) .merge("custom_field_values" => work_package.custom_value_attributes) .merge(overwritten_attributes) @@ -96,17 +96,8 @@ class WorkPackages::CopyService < BaseServices::BaseCallable attributes end - # Returns attributes that should be copied from the source work package. - # This includes writable attributes plus any auto-generated attributes - # (which will be regenerated after saving) - def copyable_work_package_attributes(work_package) - contract = instantiate_contract(work_package, user) - writable = contract.writable_attributes - - # Include auto-generated attributes so they get set (to empty) and regenerated after save - auto_generated = work_package.type.enabled_patterns.keys.map(&:to_s) || [] - - (writable + auto_generated).uniq + def writable_work_package_attributes(work_package) + instantiate_contract(work_package, user).writable_attributes end def remove_author_watcher(copied) diff --git a/spec/lib/api/v3/work_packages/schema/work_package_schema_representer_spec.rb b/spec/lib/api/v3/work_packages/schema/work_package_schema_representer_spec.rb index 41edeb517b2..cc61593774d 100644 --- a/spec/lib/api/v3/work_packages/schema/work_package_schema_representer_spec.rb +++ b/spec/lib/api/v3/work_packages/schema/work_package_schema_representer_spec.rb @@ -288,9 +288,8 @@ RSpec.describe API::V3::WorkPackages::Schema::WorkPackageSchemaRepresenter do context "on a work package which's type has an auto-generated subject" do before do allow(wp_type) - .to receive(:replacement_pattern_defined_for?) - .with(:subject) - .and_return(true) + .to receive(:enabled_patterns) + .and_return({ subject: double }) end it_behaves_like "has basic schema properties" do From a142930837bd8601f9d850f1fcd7f791318e82f5 Mon Sep 17 00:00:00 2001 From: Markus Kahl Date: Wed, 4 Feb 2026 12:42:47 +0000 Subject: [PATCH 068/293] Add tiroessler to CLA approvers list --- .github/workflows/cla.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index 3fe1d57a6f6..708cd66b65f 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -72,6 +72,7 @@ jobs: samachon, shiroginne, toy, + tiroessler, ulferts, vonTronje, vspielau, From 7038ce69ba344c3ead3f0b274e3e70b84565735a Mon Sep 17 00:00:00 2001 From: Judith Roth Date: Wed, 4 Feb 2026 17:10:39 +0100 Subject: [PATCH 069/293] Fix contributing links and update copyright year During [#71048] CLA Workflow for op-blocknote-hocuspocus https://community.openproject.org/work_packages/71048 --- CONTRIBUTING.md | 3 +-- COPYRIGHT | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 15e055791b5..b0484f3ae6f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ We are pleased that you are thinking about contributing to OpenProject! This gui ## Get in touch -Please get in touch with us using our [development forum](https://community.openproject.org/projects/openproject/boards/7) or send us an email to [info@openproject.com](mailto:info@openproject.com). +Please get in touch with us using our [OpenProject community platform](https://community.openproject.org/) or send us an email to [info@openproject.com](mailto:info@openproject.com). ## Issue tracking and coordination @@ -12,7 +12,6 @@ We eat our own ice cream so we use OpenProject for roadmap planning and team col - [Product roadmap](https://community.openproject.org/projects/openproject/roadmap) - [Wish list](https://community.openproject.org/projects/openproject/work_packages?query_id=180) -- [Bug backlog board](https://community.openproject.org/projects/openproject/boards/2905) - [Report a bug](https://www.openproject.org/docs/development/report-a-bug/) - [Submit a feature idea](https://www.openproject.org/docs/development/submit-feature-idea/) diff --git a/COPYRIGHT b/COPYRIGHT index 6e4e0e1b519..08f7c7b939c 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -1,6 +1,6 @@ OpenProject is an open source project management software. -Copyright (C) 2012-2025 the OpenProject GmbH +Copyright (C) 2012-2026 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 From 3885f1b310ea28b3231ad4a1882f85cb9c8e6eb5 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 19 Jan 2026 10:35:59 -0300 Subject: [PATCH 070/293] [#57688] Primerize Backlogs Initial pass refreshing design and implementation of Backlogs. - Reimplements Backlogs index with View Components and Turbo. - Reimplements BurodownChart with Chart.js (via ng2-charts). - Tidies up Sprint page headers. https://community.openproject.org/wp/57688 --- app/components/primer/component_helpers.rb | 41 +++ frontend/src/app/app.module.ts | 2 + .../backlogs/burndown-chart.component.html | 14 + .../backlogs/burndown-chart.component.ts | 84 +++++ .../assets/sass/backlogs/_master_backlog.sass | 299 +----------------- .../src/global_styles/primer/_overrides.sass | 13 + .../dynamic/backlogs.controller.ts | 48 +-- .../controllers/dynamic/backlogs/backlog.ts | 182 ----------- .../controllers/dynamic/backlogs/burndown.ts | 67 ---- .../dynamic/backlogs/master_backlog.ts | 42 --- .../dynamic/backlogs/story.controller.ts | 149 +++++++++ .../backlogs/taskboard-legacy.controller.ts | 18 ++ .../backlogs/backlog_component.html.erb | 67 ++++ .../components/backlogs/backlog_component.rb | 89 ++++++ .../backlog_header_component.html.erb | 104 ++++++ .../backlogs/backlog_header_component.rb | 81 +++++ .../backlogs/backlog_menu_component.html.erb | 114 +++++++ .../backlogs/backlog_menu_component.rb | 57 ++++ .../sprint_page_header_component.html.erb} | 22 +- .../backlogs/sprint_page_header_component.rb | 56 ++++ .../backlogs/story_component.html.erb} | 41 ++- .../components/backlogs/story_component.rb | 52 +++ .../backlogs/story_menu_component.html.erb} | 56 ++-- .../backlogs/story_menu_component.rb | 98 ++++++ .../controllers/rb_application_controller.rb | 4 - .../rb_master_backlogs_controller.rb | 23 +- .../app/controllers/rb_sprints_controller.rb | 76 ++++- .../app/controllers/rb_stories_controller.rb | 68 +++- .../app/forms/backlogs/backlog_header_form.rb | 81 +++++ .../app/helpers/burndown_charts_helper.rb | 45 +-- .../backlogs/app/helpers/rb_common_helper.rb | 121 +------ .../app/helpers/rb_master_backlogs_helper.rb | 121 ------- modules/backlogs/app/models/backlog.rb | 24 +- modules/backlogs/app/models/sprint.rb | 4 + .../rb_burndown_charts/_burndown.html.erb | 113 +------ .../views/rb_burndown_charts/show.html.erb | 21 +- .../views/rb_master_backlogs/index.html.erb | 91 +++--- .../app/views/rb_sprints/_sprint.html.erb | 78 ----- .../app/views/rb_stories/_helpers.html.erb | 75 ----- .../app/views/rb_stories/_story.html.erb | 79 ----- .../app/views/rb_taskboards/show.html.erb | 53 ++-- .../app/views/shared/_server_variables.js.erb | 9 +- modules/backlogs/config/locales/en.yml | 47 ++- modules/backlogs/config/locales/js-en.yml | 3 + modules/backlogs/config/routes.rb | 28 +- .../lib/open_project/backlogs/engine.rb | 8 +- .../backlogs/backlog_component_spec.rb | 149 +++++++++ .../backlogs/backlog_header_component_spec.rb | 214 +++++++++++++ .../backlogs/backlog_menu_component_spec.rb | 207 ++++++++++++ .../sprint_page_header_component_spec.rb | 123 +++++++ .../backlogs/story_component_spec.rb | 133 ++++++++ .../backlogs/story_menu_component_spec.rb | 195 ++++++++++++ .../rb_master_backlogs_controller_spec.rb | 70 ++++ .../controllers/rb_sprints_controller_spec.rb | 126 ++++++++ .../controllers/rb_stories_controller_spec.rb | 102 ++++++ .../features/backlogs/change_status_spec.rb | 2 +- .../features/backlogs/context_menu_spec.rb | 8 +- .../features/backlogs/create_story_spec.rb | 61 ++-- .../spec/features/empty_backlogs_spec.rb | 19 +- .../spec/features/stories_in_backlog_spec.rb | 157 --------- modules/backlogs/spec/models/backlog_spec.rb | 15 + .../rb_master_backlogs_routing_spec.rb | 11 + .../spec/routing/rb_sprints_routing_spec.rb | 18 ++ .../spec/routing/rb_stories_routing_spec.rb | 20 ++ .../backlogs/spec/support/pages/backlogs.rb | 39 +-- .../views/rb_burndown_charts/show_spec.rb | 2 +- 66 files changed, 3026 insertions(+), 1613 deletions(-) create mode 100644 app/components/primer/component_helpers.rb create mode 100644 frontend/src/app/features/backlogs/burndown-chart.component.html create mode 100644 frontend/src/app/features/backlogs/burndown-chart.component.ts delete mode 100644 frontend/src/stimulus/controllers/dynamic/backlogs/backlog.ts delete mode 100644 frontend/src/stimulus/controllers/dynamic/backlogs/burndown.ts delete mode 100644 frontend/src/stimulus/controllers/dynamic/backlogs/master_backlog.ts create mode 100644 frontend/src/stimulus/controllers/dynamic/backlogs/story.controller.ts create mode 100644 frontend/src/stimulus/controllers/dynamic/backlogs/taskboard-legacy.controller.ts create mode 100644 modules/backlogs/app/components/backlogs/backlog_component.html.erb create mode 100644 modules/backlogs/app/components/backlogs/backlog_component.rb create mode 100644 modules/backlogs/app/components/backlogs/backlog_header_component.html.erb create mode 100644 modules/backlogs/app/components/backlogs/backlog_header_component.rb create mode 100644 modules/backlogs/app/components/backlogs/backlog_menu_component.html.erb create mode 100644 modules/backlogs/app/components/backlogs/backlog_menu_component.rb rename modules/backlogs/app/{views/shared/_validation_errors.html.erb => components/backlogs/sprint_page_header_component.html.erb} (81%) create mode 100644 modules/backlogs/app/components/backlogs/sprint_page_header_component.rb rename modules/backlogs/app/{views/layouts/backlogs.html.erb => components/backlogs/story_component.html.erb} (57%) create mode 100644 modules/backlogs/app/components/backlogs/story_component.rb rename modules/backlogs/app/{views/rb_master_backlogs/_backlog.html.erb => components/backlogs/story_menu_component.html.erb} (54%) create mode 100644 modules/backlogs/app/components/backlogs/story_menu_component.rb create mode 100644 modules/backlogs/app/forms/backlogs/backlog_header_form.rb delete mode 100644 modules/backlogs/app/helpers/rb_master_backlogs_helper.rb delete mode 100644 modules/backlogs/app/views/rb_sprints/_sprint.html.erb delete mode 100644 modules/backlogs/app/views/rb_stories/_helpers.html.erb delete mode 100644 modules/backlogs/app/views/rb_stories/_story.html.erb create mode 100644 modules/backlogs/spec/components/backlogs/backlog_component_spec.rb create mode 100644 modules/backlogs/spec/components/backlogs/backlog_header_component_spec.rb create mode 100644 modules/backlogs/spec/components/backlogs/backlog_menu_component_spec.rb create mode 100644 modules/backlogs/spec/components/backlogs/sprint_page_header_component_spec.rb create mode 100644 modules/backlogs/spec/components/backlogs/story_component_spec.rb create mode 100644 modules/backlogs/spec/components/backlogs/story_menu_component_spec.rb create mode 100644 modules/backlogs/spec/controllers/rb_master_backlogs_controller_spec.rb create mode 100644 modules/backlogs/spec/controllers/rb_sprints_controller_spec.rb create mode 100644 modules/backlogs/spec/controllers/rb_stories_controller_spec.rb diff --git a/app/components/primer/component_helpers.rb b/app/components/primer/component_helpers.rb new file mode 100644 index 00000000000..603fde56b4d --- /dev/null +++ b/app/components/primer/component_helpers.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module Primer + module ComponentHelpers + def stack(**, &) + render(Primer::Alpha::Stack.new(**), &) + end + + def stack_item(**, &) + render(Primer::Alpha::StackItem.new(**), &) + end + end +end diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 028cdc1ed2c..802e6b3ec6e 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -206,6 +206,7 @@ import { WorkPackageFullCreateEntryComponent } from 'core-app/features/work-pack import { WorkPackageFullViewEntryComponent } from 'core-app/features/work-packages/routing/wp-full-view/wp-full-view-entry.component'; import { MyPageComponent } from './features/my-page/my-page.component'; import { DashboardComponent } from './features/overview/dashboard.component'; +import { BurndownChartComponent } from './features/backlogs/burndown-chart.component'; export function initializeServices(injector:Injector) { return () => { @@ -419,5 +420,6 @@ export class OpenProjectModule implements DoBootstrap { registerCustomElement('opce-my-page', MyPageComponent, { injector }); registerCustomElement('opce-dashboard', DashboardComponent, { injector }); + registerCustomElement('opce-burndown-chart', BurndownChartComponent, { injector }); } } diff --git a/frontend/src/app/features/backlogs/burndown-chart.component.html b/frontend/src/app/features/backlogs/burndown-chart.component.html new file mode 100644 index 00000000000..7a1b592ddee --- /dev/null +++ b/frontend/src/app/features/backlogs/burndown-chart.component.html @@ -0,0 +1,14 @@ + + +
+ +
+ Debug + +
{{maxValue() }}
+
{{lineChartData() | json}}
+
+
diff --git a/frontend/src/app/features/backlogs/burndown-chart.component.ts b/frontend/src/app/features/backlogs/burndown-chart.component.ts new file mode 100644 index 00000000000..a07ae8b41e5 --- /dev/null +++ b/frontend/src/app/features/backlogs/burndown-chart.component.ts @@ -0,0 +1,84 @@ +//-- 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. +//++ + +import { JsonPipe } from '@angular/common'; +import { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core'; +import { ChartData, ChartOptions } from 'chart.js'; +import { I18nService } from 'core-app/core/i18n/i18n.service'; +import PrimerColorsPlugin from 'core-app/shared/components/work-package-graphs/plugin.primer-colors'; +import { BaseChartDirective, provideCharts, withDefaultRegisterables } from 'ng2-charts'; + +const BURNDOWN_Y_SCALE_MIN = 25; + +@Component({ + selector: 'op-burndown-chart', + templateUrl: './burndown-chart.component.html', + imports: [BaseChartDirective, JsonPipe], + providers: [provideCharts(withDefaultRegisterables(PrimerColorsPlugin))], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class BurndownChartComponent { + readonly i18n = inject(I18nService); + readonly chartData = input.required(); + + readonly lineChartData = computed>(() => { + const data = JSON.parse(this.chartData()) as ChartData<'line'>; + return data; + }); + + readonly maxValue = computed(() => { + return this.lineChartData().datasets + .flatMap((dataset) => dataset.data) + .filter((item):item is number => typeof item === 'number') + .reduce((a, b) => Math.max(a, b), 0); + }); + + readonly lineChartOptions = computed>(() => ({ + scales: { + x: { + title: { + display: true, + text: this.i18n.t('js.burndown.day') + } + }, + y: { + title: { + display: true, + text: this.i18n.t('js.burndown.points') + }, + suggestedMin: 0, + max: this.maxValue() + BURNDOWN_Y_SCALE_MIN + } + }, + plugins: { + legend: { + position: 'top' + } + } + })); +} diff --git a/frontend/src/assets/sass/backlogs/_master_backlog.sass b/frontend/src/assets/sass/backlogs/_master_backlog.sass index a6f0a9e3cd1..576744dfda7 100644 --- a/frontend/src/assets/sass/backlogs/_master_backlog.sass +++ b/frontend/src/assets/sass/backlogs/_master_backlog.sass @@ -26,299 +26,8 @@ * See COPYRIGHT and LICENSE files for more details. ++ */ -#rb - #backlogs_container - width: 100% - display: flex - flex-wrap: wrap - justify-content: space-between - #owner_backlogs_container - min-width: 420px - order: 2 - width: 49% - flex: 0 0 49% - #sprint_backlogs_container - min-width: 420px - width: 49% - flex: 0 0 49% - min-height: 230px - #owner_backlogs_container .backlog .header > .add_new_story - height: 28px - line-height: 31px - padding: 0 - position: absolute - right: 10px - text-align: right - top: 1px - width: 100px - #backlogs_container .backlog - border: 1px solid var(--borderColor-default) - display: block - margin: 0 0 10px 0 - width: 100% +li[data-empty-list-item] + display: none -#rb - #backlogs_container .backlog .header - background-color: var(--bgColor-muted) - height: 30px - position: relative - width: 100% - .backlog .header .backlog-menu - border-right: 1px solid var(--borderColor-default) - cursor: pointer - height: 30px - overflow: visible - position: absolute - top: 0 - right: 0 - width: 30px - .icon-context - position: absolute - top: 7px - left: 12px - // Firefox wrongly positions icon - &:before - padding: 0 - &.open - &+ .items - display: block - .items - display: none - background-color: var(--overlay-bgColor) - border: 1px solid var(--borderColor-default) - position: absolute - top: 30px - right: -2px - list-style: none - margin: 0 - padding: 0 - z-index: 1000 - .item - display: block - width: 160px - height: 2rem - font-size: 0.9rem - text-align: left - text-decoration: none - vertical-align: middle - overflow: hidden - white-space: nowrap - &.hover, &:hover - background-color: #999 - a - display: block - height: 100% - padding: 6px - width: 100% - &.hover a, &:hover a - color: #FFFFFF - text-decoration: none - #backlogs_container - .backlog - .header - .velocity - height: 28px - line-height: 31px - padding: 0 3px 0 9px - position: absolute - right: 25px - text-align: right - top: 0px - width: 32px - .toggler - font-family: "openproject-icon-font" - height: 30px - line-height: 31px - padding: 0 - position: absolute - left: 0 - top: 0 - width: 23px - cursor: pointer - &:before - position: absolute - left: 6px - top: 10px - &.closed:before - position: absolute - left: 6px - top: 10px - &:hover - cursor: pointer - background-color: #D8D8D8 - .sprint - background-color: transparent - cursor: pointer - display: block - height: 29px - width: auto - margin-left: 30px - margin-right: 50px - &.error.icon-bug - background: none - text-align: center - &:before - position: absolute - color: red - .id, .status - display: none - - .name - line-height: 2rem - font-weight: var(--base-text-weight-bold) - overflow: hidden - white-space: nowrap - margin-left: 0.5em - - .start_date, .effective_date - float: right - height: 28px - line-height: 2rem - width: 6.5em - margin-left: 0.5em - .stories - list-style: none - min-height: 2rem - margin: 0 - padding: 0 0 0px 0 - z-index: 500 - overflow-y: auto - overflow-x: hidden - &.closed - display: none - - .error.icon.icon-bug - text-align: left - .stories:not(.prevent_drag) .story - cursor: move - .stories .story - display: block - font-size: 0.9rem - margin: 0 - overflow: hidden - position: relative - width: 100% - &.odd - background-color: var(--bgColor-neutral-muted) - &.even - background-color: var(--body-background) - &.error.icon-bug - background: none - text-align: center - &:before - position: absolute - color: red - pointer-events: none - &.hover, &:hover - background-color: var(--highlight-neutral-bgColor) - &.closed - text-decoration: line-through - .id - float: left - margin-left: 1em - margin-right: 1em - padding: 5px 2px 4px 2px - width: 4em - text-align: right - white-space: nowrap - .type_id .t - float: left - padding: 5px 2px 4px 2px - text-align: right - white-space: nowrap - .subject - overflow: hidden - margin-left: 4em - padding: 5px 2px 4px 2px - white-space: nowrap - min-height: 1em - .status_id - float: right - padding: 5px 2px 4px 2px - margin-left: 1em - width: 68px - .story_points - float: right - padding: 5px 1rem 4px 2px - width: 3.5rem - min-height: 14px - height: 2rem - text-align: center - .type_id .v, .id .v, .status_id .v, .version_id, .higher_item_id - display: none - -.rb_dialog - .burndown_chart - margin-top: 20px - margin-bottom: 20px - margin-left: 20px - #charts - h3 - border: 0px - overflow: hidden - fieldset.burndown_control - padding-left: 10px - border: none - .axislabel - font-weight: var(--base-text-weight-bold) - -/* In-place Sprint Editor */ - -#rb #backlogs_container - .sprint.editing - .editors, > .editor - display: block - label, > * - display: none - + - .velocity, .add_new_story - display: none - .backlog .sprint.editing - .editors - display: flex - align-items: center - flex-direction: row-reverse - - .editor - font-size: 0.9rem - line-height: 1.5rem - height: 30px - margin: 0 - padding: 0 - - &.name - flex-basis: 15em - &.start_date, - &.effective_date - margin-left: 0.5em - flex-basis: 12.5em - - .stories .story.editing - > - *, .editors label - display: none - .editors - display: block - select, input - display: inline-block - float: none - margin: 5px 3px 4px 2px - font-size: 0.8rem - // reset the line-height (foundation sets it to "normal" but that does not work here) - line-height: inherit - .type_id.editor - width: 15% - /* sets max-width for IE */ - max-width: 140px - /* for the cool guys */ - .subject.editor - width: 55% - .status_id.editor - width: 15% - float: right - .story_points.editor - float: right - width: 10% - -.backlog - font-size: 0.9rem +li[data-empty-list-item]:only-child + display: list-item diff --git a/frontend/src/global_styles/primer/_overrides.sass b/frontend/src/global_styles/primer/_overrides.sass index 5dec0007aaa..f5c91030591 100644 --- a/frontend/src/global_styles/primer/_overrides.sass +++ b/frontend/src/global_styles/primer/_overrides.sass @@ -152,3 +152,16 @@ ul.SegmentedControl, .ActionListContent[aria-disabled="true"] .ActionListItem-label[class^="__hl_"], .ActionListItem-label[class*=" __hl_"] color: var(--control-fgColor-disabled) !important + +.Box-row--focus-blue + &:focus-visible + background-color: var(--bgColor-accent-muted) + +.Box-row--clickable + cursor: pointer + +.Box-row--draggable + padding-left: calc(var(--stack-padding-normal) / 2) + +.Box-header--collapsible + padding-left: calc(var(--stack-padding-normal) / 2) diff --git a/frontend/src/stimulus/controllers/dynamic/backlogs.controller.ts b/frontend/src/stimulus/controllers/dynamic/backlogs.controller.ts index a551ee1768e..b2ed8978f20 100644 --- a/frontend/src/stimulus/controllers/dynamic/backlogs.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/backlogs.controller.ts @@ -1,24 +1,32 @@ +//-- 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. +//++ + import { Controller } from '@hotwired/stimulus'; -import 'jquery.flot'; -import 'jquery.flot/excanvas'; - -import 'core-vendor/jquery.jeditable.mini'; -import 'core-vendor/jquery.colorcontrast'; - -import './backlogs/common'; -import './backlogs/master_backlog'; -import './backlogs/backlog'; -import './backlogs/burndown'; -import './backlogs/model'; -import './backlogs/editable_inplace'; -import './backlogs/sprint'; -import './backlogs/work_package'; -import './backlogs/story'; -import './backlogs/task'; -import './backlogs/impediment'; -import './backlogs/taskboard'; -import './backlogs/show_main'; - export default class BacklogsController extends Controller { } diff --git a/frontend/src/stimulus/controllers/dynamic/backlogs/backlog.ts b/frontend/src/stimulus/controllers/dynamic/backlogs/backlog.ts deleted file mode 100644 index 91cb0937b8c..00000000000 --- a/frontend/src/stimulus/controllers/dynamic/backlogs/backlog.ts +++ /dev/null @@ -1,182 +0,0 @@ -//-- copyright -// OpenProject is an open source project management software. -// Copyright (C) the OpenProject GmbH -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License version 3. -// -// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: -// Copyright (C) 2006-2013 Jean-Philippe Lang -// Copyright (C) 2010-2013 the ChiliProject Team -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -// -// See COPYRIGHT and LICENSE files for more details. -//++ - -/****************************************** - BACKLOG - A backlog is a visual representation of - a sprint and its stories. It is not a - sprint. Imagine it this way: A sprint is - a start and end date, and a set of - objectives. A backlog is something you - would draw up on the board or a spread- - sheet (or in Redmine Backlogs!) to - visualize the sprint. -******************************************/ - -// @ts-expect-error TS(2304): Cannot find name 'RB'. -RB.Backlog = (function ($) { - // @ts-expect-error TS(2304): Cannot find name 'RB'. - return RB.Object.create({ - - initialize(el:any) { - this.$ = $(el); - this.el = el; - - // Associate this object with the element for later retrieval - this.$.data('this', this); - - // Make the list sortable - this.getList().sortable({ - connectWith: '.stories', - dropOnEmpty: true, - start: this.dragStart, - stop: this.dragStop, - update: this.dragComplete, - receive: this.dragChanged, - remove: this.dragChanged, - containment: $('#backlogs_container'), - cancel: 'input, textarea, button, select, option, .prevent_drag', - scroll: true, - helper(event:any, ui:any) { - const $clone = $(ui).clone(); - $clone.css('position', 'absolute'); - return $clone.get(0); - }, - }); - - // Observe menu items - this.$.find('.add_new_story').click(this.handleNewStoryClick); - - if (this.isSprintBacklog()) { - // @ts-expect-error TS(2304): Cannot find name 'RB'. - RB.Factory.initialize(RB.Sprint, this.getSprint()); - // @ts-expect-error TS(2304): Cannot find name 'RB'. - this.burndown = RB.Factory.initialize(RB.Burndown, this.$.find('.show_burndown_chart')); - this.burndown.setSprintId(this.getSprint().data('this').getID()); - } - - // Initialize each item in the backlog - this.getStories().each(function (this:any, index:any) { - // 'this' refers to an element with class="story" - // @ts-expect-error TS(2304): Cannot find name 'RB'. - RB.Factory.initialize(RB.Story, this); - }); - - if (this.isSprintBacklog()) { - this.refresh(); - } - }, - - dragChanged(e:any, ui:any) { - $(this).parents('.backlog').data('this').refresh(); - }, - - dragComplete(e:any, ui:any) { - const isDropTarget = (ui.sender === null || ui.sender === undefined); - - // jQuery triggers dragComplete of source and target. - // Thus we have to check here. Otherwise, the story - // would be saved twice. - if (isDropTarget) { - ui.item.data('this').saveDragResult(); - } - }, - - dragStart(e:any, ui:any) { - ui.item.addClass('dragging'); - }, - - dragStop(e:any, ui:any) { - ui.item.removeClass('dragging'); - }, - - getSprint() { - return $(this.el).find('.model.sprint').first(); - }, - - getStories() { - return this.getList().children('.story'); - }, - - getList() { - return this.$.children('.stories').first(); - }, - - handleNewStoryClick(e:any) { - const toggler = $(this).parents('.header').find('.toggler'); - if (toggler.hasClass('closed')) { - toggler.click(); - } - e.preventDefault(); - $(this).parents('.backlog').data('this').newStory(); - }, - - // return true if backlog has an element with class="sprint" - isSprintBacklog() { - return $(this.el).find('.sprint').length === 1; - }, - - newStory() { - let story; - let o; - - story = $('#story_template').children().first().clone(); - this.getList().prepend(story); - - // @ts-expect-error TS(2304): Cannot find name 'RB'. - o = RB.Factory.initialize(RB.Story, story[0]); - o.edit(); - - story.find('.editor').first().focus(); - }, - - refresh() { - this.recalcVelocity(); - this.recalcOddity(); - }, - - recalcVelocity() { - let total:any; - - if (!this.isSprintBacklog()) { - return; - } - - total = 0; - this.getStories().each(function (this:any, index:any) { - total += $(this).data('this').getPoints(); - }); - this.$.children('.header').children('.velocity').text(total); - }, - - recalcOddity() { - this.$.find('.story:even').removeClass('odd').addClass('even'); - this.$.find('.story:odd').removeClass('even').addClass('odd'); - }, - }); -}(jQuery)); diff --git a/frontend/src/stimulus/controllers/dynamic/backlogs/burndown.ts b/frontend/src/stimulus/controllers/dynamic/backlogs/burndown.ts deleted file mode 100644 index b6942b86676..00000000000 --- a/frontend/src/stimulus/controllers/dynamic/backlogs/burndown.ts +++ /dev/null @@ -1,67 +0,0 @@ -//-- copyright -// OpenProject is an open source project management software. -// Copyright (C) the OpenProject GmbH -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License version 3. -// -// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: -// Copyright (C) 2006-2013 Jean-Philippe Lang -// Copyright (C) 2010-2013 the ChiliProject Team -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -// -// See COPYRIGHT and LICENSE files for more details. -//++ - -// @ts-expect-error TS(2304): Cannot find name 'RB'. -RB.Burndown = (function ($) { - // @ts-expect-error TS(2304): Cannot find name 'RB'. - return RB.Object.create({ - - initialize(el:any) { - this.$ = $(el); - this.el = el; - - // Associate this object with the element for later retrieval - this.$.data('this', this); - - // Observe menu items - this.$.click(this.show); - }, - - setSprintId(sprintId:any) { - this.sprintId = sprintId; - }, - - getSprintId() { - return this.sprintId; - }, - - show(e:any) { - e.preventDefault(); - - if ($('#charts').length === 0) { - $('
').appendTo('body'); - } - // @ts-expect-error TS(2304): Cannot find name 'RB'. - $('#charts').html(`
${RB.i18n.generating_graph}
`); - - // @ts-expect-error TS(2304): Cannot find name 'RB'. - const url = RB.urlFor('show_burndown_chart', { sprint_id: $(this).data('this').sprintId, project_id: RB.constants.project_id }); - window.open(url); - }, - }); -}(jQuery)); diff --git a/frontend/src/stimulus/controllers/dynamic/backlogs/master_backlog.ts b/frontend/src/stimulus/controllers/dynamic/backlogs/master_backlog.ts deleted file mode 100644 index 9f055c6bcfc..00000000000 --- a/frontend/src/stimulus/controllers/dynamic/backlogs/master_backlog.ts +++ /dev/null @@ -1,42 +0,0 @@ -//-- copyright -// OpenProject is an open source project management software. -// Copyright (C) the OpenProject GmbH -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License version 3. -// -// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: -// Copyright (C) 2006-2013 Jean-Philippe Lang -// Copyright (C) 2010-2013 the ChiliProject Team -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -// -// See COPYRIGHT and LICENSE files for more details. -//++ - -// Initialize the backlogs after DOM is loaded -jQuery(($) => { - // Initialize each backlog - $('.backlog').each(function (index) { - // 'this' refers to an element with class="backlog" - // @ts-expect-error TS(2304): Cannot find name 'RB'. - RB.Factory.initialize(RB.Backlog, this); - }); - - $('.backlog .toggler').on('click', function () { - $(this).toggleClass('closed icon-arrow-up1 icon-arrow-down1'); - $(this).parents('.backlog').find('ul.stories').toggleClass('closed'); - }); -}); diff --git a/frontend/src/stimulus/controllers/dynamic/backlogs/story.controller.ts b/frontend/src/stimulus/controllers/dynamic/backlogs/story.controller.ts new file mode 100644 index 00000000000..9340149f9fe --- /dev/null +++ b/frontend/src/stimulus/controllers/dynamic/backlogs/story.controller.ts @@ -0,0 +1,149 @@ +//-- 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. +//++ + +import { Controller } from '@hotwired/stimulus'; +import * as Turbo from '@hotwired/turbo'; + +export default class StoryController extends Controller implements EventListenerObject { + static values = { + splitUrl: String, + fullUrl: String, + }; + + declare splitUrlValue:string; + declare fullUrlValue:string; + + private abortController:AbortController|null = null; + private clickTimeout:number|null = null; + + connect():void { + this.abortController = new AbortController(); + const { signal } = this.abortController; + + this.element.addEventListener('click', this, { signal }); + this.element.addEventListener('dblclick', this, { signal }); + this.element.addEventListener('keydown', this, { signal }); + } + + disconnect():void { + this.abortController?.abort(); + this.abortController = null; + + if (this.clickTimeout !== null) { + clearTimeout(this.clickTimeout); + this.clickTimeout = null; + } + } + + handleEvent(event:Event):void { + switch (event.type) { + case 'click': + this.onClick(event as MouseEvent); + break; + case 'dblclick': + this.onDblClick(event as MouseEvent); + break; + case 'keydown': + this.onKeydown(event as KeyboardEvent); + break; + } + } + + private onClick(event:MouseEvent):void { + const target = event.target; + if (!(target instanceof HTMLElement)) return; + + if ( + target.closest('a') || + target.closest('button') || + target.closest('[data-drag-handle]') + ) { + return; + } + + if (this.clickTimeout !== null) return; + + this.clickTimeout = window.setTimeout(() => { + this.clickTimeout = null; + this.openSplitPane(); + }, 250); + } + + private onDblClick(event:MouseEvent):void { + const target = event.target; + if (!(target instanceof HTMLElement)) return; + + if ( + target.closest('a') || + target.closest('button') || + target.closest('[data-drag-handle]') + ) { + return; + } + + if (this.clickTimeout !== null) { + clearTimeout(this.clickTimeout); + this.clickTimeout = null; + } + + this.openFullPane(); + } + + private onKeydown(event:KeyboardEvent):void { + if (event.key !== 'Enter') return; + + const target = event.target; + if (!(target instanceof HTMLElement)) return; + + if ( + target.closest('a') || + target.closest('button') || + target.closest('input') || + target.closest('textarea') || + target.closest('select') || + target.closest("[contenteditable='true']") + ) { + return; + } + + event.preventDefault(); + if (event.shiftKey) { + this.openFullPane(); + } else { + this.openSplitPane(); + } + } + + private openSplitPane():void { + Turbo.visit(this.splitUrlValue, { frame: 'content-bodyRight', action: 'advance' }); + } + + private openFullPane():void { + Turbo.visit(this.fullUrlValue, { frame: '_top' }); + } +} diff --git a/frontend/src/stimulus/controllers/dynamic/backlogs/taskboard-legacy.controller.ts b/frontend/src/stimulus/controllers/dynamic/backlogs/taskboard-legacy.controller.ts new file mode 100644 index 00000000000..ed66caeff51 --- /dev/null +++ b/frontend/src/stimulus/controllers/dynamic/backlogs/taskboard-legacy.controller.ts @@ -0,0 +1,18 @@ +import { Controller } from '@hotwired/stimulus'; + +import 'core-vendor/jquery.jeditable.mini'; +import 'core-vendor/jquery.colorcontrast'; + +import './common'; +import './model'; +import './editable_inplace'; +import './sprint'; +import './work_package'; +import './story'; +import './task'; +import './impediment'; +import './taskboard'; +import './show_main'; + +export default class TaskboardLegacyController extends Controller { +} diff --git a/modules/backlogs/app/components/backlogs/backlog_component.html.erb b/modules/backlogs/app/components/backlogs/backlog_component.html.erb new file mode 100644 index 00000000000..bca17b3bff1 --- /dev/null +++ b/modules/backlogs/app/components/backlogs/backlog_component.html.erb @@ -0,0 +1,67 @@ +<%# -- 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. + +++# %> + +<%= component_wrapper(tag: :section) do %> + <%= render(Primer::Beta::BorderBox.new(**@system_arguments)) do |border_box| %> + <% border_box.with_header(classes: "Box-header--collapsible") do %> + <%= render(Backlogs::BacklogHeaderComponent.new(backlog:, project: @project, folded: folded?)) %> + <% end %> + <% border_box.with_row(data: { empty_list_item: true }) do %> + <% if backlog.sprint_backlog? %> + <%= + render Primer::Beta::Blankslate.new(role: "status", aria: { live: "polite" }) do |blankslate| + blankslate.with_heading(tag: :h4).with_content(t(".sprint_backlog.blankslate_title")) + blankslate.with_description_content(t(".sprint_backlog.blankslate_description")) + end + %> + <% else %> + <%= + render Primer::Beta::Blankslate.new(role: "status", aria: { live: "polite" }) do |blankslate| + blankslate.with_heading(tag: :h4).with_content(t(".product_backlog.blankslate_title")) + blankslate.with_description_content(t(".product_backlog.blankslate_description")) + end + %> + <% end %> + <% end %> + <% backlog.stories.each do |story| %> + <% border_box.with_row( + id: dom_id(story), + classes: "Box-row--hover-gray Box-row--focus-blue Box-row--clickable Box-row--draggable", + data: draggable_item_config(story).merge( + controller: "backlogs--story", + backlogs__story_split_url_value: details_backlogs_project_backlogs_path(project, story), + backlogs__story_full_url_value: work_package_path(story) + ), + tabindex: 0 + ) do %> + <%= render(Backlogs::StoryComponent.new(story:, sprint:, max_position:)) %> + <% end %> + <% end %> + <% end %> +<% end %> diff --git a/modules/backlogs/app/components/backlogs/backlog_component.rb b/modules/backlogs/app/components/backlogs/backlog_component.rb new file mode 100644 index 00000000000..e313299ba64 --- /dev/null +++ b/modules/backlogs/app/components/backlogs/backlog_component.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module Backlogs + class BacklogComponent < ApplicationComponent + include Primer::AttributesHelper + include Primer::ComponentHelpers + include OpTurbo::Streamable + include RbCommonHelper + + attr_reader :backlog, :project, :current_user + + delegate :sprint, :stories, to: :backlog + + def initialize(backlog:, project:, current_user: User.current, **system_arguments) + super() + + @backlog = backlog + @project = project + @current_user = current_user + + @system_arguments = system_arguments + @system_arguments[:id] = dom_id(backlog) + @system_arguments[:list_id] = "#{@system_arguments[:id]}-list" + @system_arguments[:data] = merge_data( + @system_arguments, + { data: drop_target_config } + ) + end + + def wrapper_uniq_by + backlog.sprint_id + end + + private + + def folded? + current_user.backlogs_preference(:versions_default_fold_state) == "closed" + end + + def max_position + stories.map(&:position).max + end + + def drop_target_config + { + generic_drag_and_drop_target: "container", + target_container_accessor: ":scope > ul", + target_id: backlog.sprint_id, + target_allowed_drag_type: "story" + } + end + + def draggable_item_config(story) + { + draggable_id: story.id, + draggable_type: "story", + drop_url: move_backlogs_project_sprint_story_path(project, sprint, story) + } + end + end +end diff --git a/modules/backlogs/app/components/backlogs/backlog_header_component.html.erb b/modules/backlogs/app/components/backlogs/backlog_header_component.html.erb new file mode 100644 index 00000000000..05729bfb7ce --- /dev/null +++ b/modules/backlogs/app/components/backlogs/backlog_header_component.html.erb @@ -0,0 +1,104 @@ +<%# -- 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. + +++# %> + +<%= component_wrapper(tag: :header) do %> + <% if show? %> + <%= + stack( + tag: :"collapsible-header", + direction: :horizontal, + align: :center, + justify: :space_between, + classes: class_names( + "CollapsibleHeader", + "CollapsibleHeader--collapsed" => @collapsed + ), + data: { collapsed: (@collapsed if @collapsed) } + ) do + %> + <%= stack_item(classes: "hide-when-print") do %> + <%= + render( + Primer::BaseComponent.new( + tag: :div, + role: "button", + tabindex: 0, + classes: "CollapsibleSection--triggerArea", + aria: { + label: t(".label_toggle_backlog", name: sprint.name), + controls: "#{dom_id(backlog)}-list", + expanded: !@collapsed + }, + data: { + target: "collapsible-header.triggerElement", + action: "click:collapsible-header#toggle keydown:collapsible-header#toggleViaKeyboard" + } + ) + ) do + %> + <%= render(Primer::Beta::Octicon.new(icon: "chevron-up", hidden: @collapsed, data: { target: "collapsible-header.arrowUp" })) %> + <%= render(Primer::Beta::Octicon.new(icon: "chevron-down", hidden: !@collapsed, data: { target: "collapsible-header.arrowDown" })) %> + <% end %> + <% end %> + <%= stack_item(grow: true) do %> + <%= stack(direction: :horizontal, align: :center) do %> + <%= render Primer::Beta::Truncate.new(tag: :h4, classes: "Box-title") do %> + <%= sprint.name %> + <% end %> + + <%= stack_item do %> + <%= render(Primer::Beta::Counter.new(count: story_count, round: true)) %> + <% end %> + + <%= stack_item(grow: true) do %> + <%= render(Primer::Beta::Text.new(color: :subtle, role: "group")) do %> + <%= format_date_range(date_range) %> + <% end %> + <% end %> + <% end %> + <% end %> + + <%= render(Primer::Beta::Truncate.new(color: :subtle, classes: "velocity")) do %> + <%= t(:"backlogs.points", count: story_points) %> + <% end %> + + <%= render(Backlogs::BacklogMenuComponent.new(backlog:, project: @project)) %> + <% end %> + <% else %> + <%= + primer_form_with( + url: backlogs_project_sprint_path(project, sprint), + model: sprint, + method: :patch + ) do |f| + render(Backlogs::BacklogHeaderForm.new(f, cancel_path: show_name_backlogs_project_sprint_path(project, sprint))) + end + %> + <% end %> +<% end %> diff --git a/modules/backlogs/app/components/backlogs/backlog_header_component.rb b/modules/backlogs/app/components/backlogs/backlog_header_component.rb new file mode 100644 index 00000000000..0e73c686412 --- /dev/null +++ b/modules/backlogs/app/components/backlogs/backlog_header_component.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module Backlogs + class BacklogHeaderComponent < ApplicationComponent + include OpTurbo::Streamable + include Primer::ComponentHelpers + include Redmine::I18n + include RbCommonHelper + + STATE_DEFAULT = :show + STATE_OPTIONS = [STATE_DEFAULT, :edit].freeze + + attr_reader :backlog, :project, :state, :collapsed, :current_user + + delegate :sprint, :stories, to: :backlog + delegate :name, to: :sprint, prefix: :sprint + delegate :edit?, :show?, to: :state + + def initialize( + backlog:, + project:, + state: STATE_DEFAULT, + folded: false, + current_user: User.current + ) + super() + + @backlog = backlog + @project = project + @state = ActiveSupport::StringInquirer.new(state.to_s) + @collapsed = folded + @current_user = current_user + end + + def wrapper_uniq_by + backlog.sprint_id + end + + private + + def story_points + @story_points ||= stories.sum { |story| story.story_points || 0 } + end + + def story_count + @story_count ||= stories.size + end + + def date_range + [sprint.start_date, sprint.effective_date].compact + end + end +end diff --git a/modules/backlogs/app/components/backlogs/backlog_menu_component.html.erb b/modules/backlogs/app/components/backlogs/backlog_menu_component.html.erb new file mode 100644 index 00000000000..416e57ffdad --- /dev/null +++ b/modules/backlogs/app/components/backlogs/backlog_menu_component.html.erb @@ -0,0 +1,114 @@ +<%# -- 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. + +++# %> + +<%= + render(Primer::Alpha::ActionMenu.new(anchor_align: :end, classes: "hide-when-print")) do |menu| + menu.with_show_button( + scheme: :invisible, + icon: :"kebab-horizontal", + "aria-label": t(".label_actions"), + tooltip_direction: :se + ) + + if user_allowed?(:update_sprints) + menu.with_item( + label: t(".action_menu.edit_sprint"), + href: edit_name_backlogs_project_sprint_path(project, sprint), + content_arguments: { data: { turbo_stream: true } } + ) do |item| + item.with_leading_visual_icon(icon: :pencil) + end + + menu.with_divider + end + + if user_allowed?(:add_work_packages) + menu.with_item( + label: t(:"backlogs.add_new_story"), + href: new_project_work_packages_dialog_path( + project, + version_id: sprint.id, + type_id: available_story_types.first + ), + content_arguments: { data: { turbo_stream: true } } + ) do |item| + item.with_leading_visual_icon(icon: :compose) + end + end + + menu.with_item( + label: t(:label_stories_tasks), + tag: :a, + href: backlogs_project_sprint_query_path(project, sprint) + ) do |item| + item.with_leading_visual_icon(icon: :"op-view-list") + end + + if user_allowed?(:manage_versions) + menu.with_item( + label: t(:"backlogs.properties"), + tag: :a, + href: edit_version_path(sprint, back_url: backlogs_project_backlogs_path(project), project_id: project.id) + ) do |item| + item.with_leading_visual_icon(icon: :gear) + end + end + + if backlog.sprint_backlog? + if user_allowed?(:view_taskboards) + menu.with_item( + label: t(:label_task_board), + tag: :a, + href: backlogs_project_sprint_taskboard_path(project, sprint) + ) do |item| + item.with_leading_visual_icon(icon: :"op-view-cards") + end + end + + menu.with_item( + label: t(:"backlogs.show_burndown_chart"), + tag: :a, + href: backlogs_project_sprint_burndown_chart_path(project, sprint), + disabled: !sprint.has_burndown? + ) do |item| + item.with_leading_visual_icon(icon: :graph) + end + + if project.module_enabled? "wiki" + menu.with_item( + label: t(:label_wiki), + tag: :a, + href: edit_backlogs_project_sprint_wiki_path(project, sprint) + ) do |item| + item.with_leading_visual_icon(icon: :book) + end + end + end + end +%> diff --git a/modules/backlogs/app/components/backlogs/backlog_menu_component.rb b/modules/backlogs/app/components/backlogs/backlog_menu_component.rb new file mode 100644 index 00000000000..07f5ad79bad --- /dev/null +++ b/modules/backlogs/app/components/backlogs/backlog_menu_component.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module Backlogs + class BacklogMenuComponent < ApplicationComponent + include RbCommonHelper + + attr_reader :backlog, :project, :current_user + + delegate :sprint, :stories, to: :backlog + + def initialize(backlog:, project:, current_user: User.current) + super() + + @backlog = backlog + @project = project + @current_user = current_user + end + + private + + def user_allowed?(permission) + current_user.allowed_in_project?(permission, project) + end + + def available_story_types + @available_story_types ||= story_types & project.types + end + end +end diff --git a/modules/backlogs/app/views/shared/_validation_errors.html.erb b/modules/backlogs/app/components/backlogs/sprint_page_header_component.html.erb similarity index 81% rename from modules/backlogs/app/views/shared/_validation_errors.html.erb rename to modules/backlogs/app/components/backlogs/sprint_page_header_component.html.erb index 8837b4d6ba7..7ddc8a20264 100644 --- a/modules/backlogs/app/views/shared/_validation_errors.html.erb +++ b/modules/backlogs/app/components/backlogs/sprint_page_header_component.html.erb @@ -1,4 +1,4 @@ -<%#-- copyright +<%# -- copyright OpenProject is an open source project management software. Copyright (C) the OpenProject GmbH @@ -25,12 +25,16 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. See COPYRIGHT and LICENSE files for more details. -++#%> +++# %> -<%= validation_errors.length > 1 ? t(:error_intro_plural) : t(:error_intro_singular) %> -
    - <%- validation_errors.each_full do |msg| %> -
  • <%= msg %>
  • - <%- end %> -
-<%= t(:error_outro) %> +<%= + render Primer::OpenProject::PageHeader.new do |header| + header.with_title_content(@sprint.name) + + header.with_description do + format_date_range(date_range) + end + + header.with_breadcrumbs(breadcrumb_items) + end +%> diff --git a/modules/backlogs/app/components/backlogs/sprint_page_header_component.rb b/modules/backlogs/app/components/backlogs/sprint_page_header_component.rb new file mode 100644 index 00000000000..3fcba09e618 --- /dev/null +++ b/modules/backlogs/app/components/backlogs/sprint_page_header_component.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +# ++ + +module Backlogs + class SprintPageHeaderComponent < ApplicationComponent + include Primer::ComponentHelpers + include ApplicationHelper + include RbCommonHelper + + def initialize(sprint:, project:) + super + + @sprint = sprint + @project = project + end + + def breadcrumb_items + [{ href: project_overview_path(@project.id), text: @project.name }, + { href: backlogs_project_backlogs_path(@project), text: t(:label_backlogs) }, + @sprint.name] + end + + private + + def date_range + [@sprint.start_date, @sprint.effective_date].compact + end + end +end diff --git a/modules/backlogs/app/views/layouts/backlogs.html.erb b/modules/backlogs/app/components/backlogs/story_component.html.erb similarity index 57% rename from modules/backlogs/app/views/layouts/backlogs.html.erb rename to modules/backlogs/app/components/backlogs/story_component.html.erb index fb3aeec57ad..d9cbaf2a9bc 100644 --- a/modules/backlogs/app/views/layouts/backlogs.html.erb +++ b/modules/backlogs/app/components/backlogs/story_component.html.erb @@ -1,4 +1,4 @@ -<%#-- copyright +<%# -- copyright OpenProject is an open source project management software. Copyright (C) the OpenProject GmbH @@ -25,16 +25,33 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. See COPYRIGHT and LICENSE files for more details. -++#%> +++# %> -<% content_for :header_tags do %> - <%= frontend_stylesheet_link_tag "backlogs.css" %> +<%= stack(tag: :article, direction: :horizontal, justify: :space_between) do %> + <%= stack_item(classes: "hide-when-print") do %> + <%= + render( + Primer::OpenProject::DragHandle.new( + role: "button", + tabindex: 0, + aria: { + label: t(".label_drag_story", name: story.subject) + } + ) + ) + %> + <% end %> + + <%= stack_item(grow: true) do %> + <%= render(WorkPackages::InfoLineComponent.new(work_package: story)) %> + <%= render(Primer::Beta::Text.new(font_weight: :semibold)) do %> + <%= story.subject %> + <% end %> + <% end %> + + <%= render(Primer::Beta::Truncate.new(color: :subtle, mt: 1)) do %> + <%= t(:"backlogs.points", count: story_points) %> + <% end %> + + <%= render(Backlogs::StoryMenuComponent.new(story:, sprint:, max_position:)) %> <% end %> - -<% content_for :additional_js_dom_ready do %> - <%= render(partial: "shared/server_variables", formats: [:js]) %> -<% end %> - -<% content_controller "backlogs" %> - -<%= render template: "layouts/base", locals: local_assigns.merge(turbo_opt_out: true) %> diff --git a/modules/backlogs/app/components/backlogs/story_component.rb b/modules/backlogs/app/components/backlogs/story_component.rb new file mode 100644 index 00000000000..2a22543cdcd --- /dev/null +++ b/modules/backlogs/app/components/backlogs/story_component.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module Backlogs + class StoryComponent < ApplicationComponent + include Primer::ComponentHelpers + + attr_reader :story, :sprint, :max_position, :current_user + + def initialize(story:, sprint:, max_position:, current_user: User.current) + super() + + @story = story + @sprint = sprint + @max_position = max_position + @current_user = current_user + end + + private + + def story_points + story.story_points || 0 + end + end +end diff --git a/modules/backlogs/app/views/rb_master_backlogs/_backlog.html.erb b/modules/backlogs/app/components/backlogs/story_menu_component.html.erb similarity index 54% rename from modules/backlogs/app/views/rb_master_backlogs/_backlog.html.erb rename to modules/backlogs/app/components/backlogs/story_menu_component.html.erb index 77d2f4f2970..424bb34efbd 100644 --- a/modules/backlogs/app/views/rb_master_backlogs/_backlog.html.erb +++ b/modules/backlogs/app/components/backlogs/story_menu_component.html.erb @@ -1,4 +1,4 @@ -<%#-- copyright +<%# -- copyright OpenProject is an open source project management software. Copyright (C) the OpenProject GmbH @@ -25,27 +25,37 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. See COPYRIGHT and LICENSE files for more details. -++#%> +++# %> -<% - folded = current_user.backlogs_preference(:versions_default_fold_state) == "closed" - editable = User.current.allowed_in_project?(:edit_work_packages, @project) +<%= + render(Primer::Alpha::ActionMenu.new(anchor_align: :end, classes: "hide-when-print")) do |menu| + menu.with_show_button( + scheme: :invisible, + icon: :"kebab-horizontal", + "aria-label": t(".label_actions"), + tooltip_direction: :se + ) + + menu.with_item( + tag: :a, + label: t(:"js.button_open_details"), + href: details_backlogs_project_backlogs_path(project, story), + content_arguments: { turbo_frame: "content-bodyRight", turbo_action: "advance" } + ) do |item| + item.with_leading_visual_icon(icon: :"op-view-split") + end + + menu.with_item( + tag: :a, + label: t(:"js.button_open_fullscreen"), + href: work_package_path(story), + content_arguments: { turbo_frame: "_top" } + ) do |item| + item.with_leading_visual_icon(icon: :"screen-full") + end + + menu.with_divider + + build_move_menu(menu) + end %> -
-
- <% icon = folded ? "icon-arrow-down1" : "icon-arrow-up1" %> -
">
- <%= render partial: "rb_sprints/sprint", object: backlog.sprint %> -
- <%= render_backlog_menu backlog %> -
-
    <%= " prevent_drag" unless editable %>"> - <% reset_cycle "stories" %> - <% backlog.stories.each_with_index do |story, index| %> - <% higher_item = index == 0 ? nil : backlog.stories[index - 1] %> - - <%= render partial: "rb_stories/story", - locals: { story:, higher_item: } %> - <% end %> -
-
diff --git a/modules/backlogs/app/components/backlogs/story_menu_component.rb b/modules/backlogs/app/components/backlogs/story_menu_component.rb new file mode 100644 index 00000000000..c65d2ac9143 --- /dev/null +++ b/modules/backlogs/app/components/backlogs/story_menu_component.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module Backlogs + class StoryMenuComponent < ApplicationComponent + attr_reader :story, :sprint, :project, :max_position, :current_user + + def initialize(story:, sprint:, max_position:, current_user: User.current) + super() + + @story = story + @sprint = sprint + @project = sprint.project + @max_position = max_position + @current_user = current_user + end + + private + + def build_move_menu(menu) + build_move_item( + menu, + label: I18n.t(:label_sort_highest), + direction: "highest", + icon: :"move-to-top", + disabled: first_item? + ) + build_move_item( + menu, + label: I18n.t(:label_sort_higher), + direction: "higher", + icon: :"chevron-up", + disabled: first_item? + ) + build_move_item( + menu, + label: I18n.t(:label_sort_lower), + direction: "lower", + icon: :"chevron-down", + disabled: last_item? + ) + build_move_item( + menu, + label: I18n.t(:label_sort_lowest), + direction: "lowest", + icon: :"move-to-bottom", + disabled: last_item? + ) + end + + def build_move_item(menu, label:, direction:, icon:, **) + menu.with_item( + label:, + tag: :button, + href: reorder_backlogs_project_sprint_story_path(project, sprint, story), + form_arguments: { method: :post, inputs: [{ name: "direction", value: direction }] }, + ** + ) do |item| + item.with_leading_visual_icon(icon:) + end + end + + def first_item? + story.position == 1 + end + + def last_item? + story.position == max_position + end + end +end diff --git a/modules/backlogs/app/controllers/rb_application_controller.rb b/modules/backlogs/app/controllers/rb_application_controller.rb index 37e0502db72..b756ae04b64 100644 --- a/modules/backlogs/app/controllers/rb_application_controller.rb +++ b/modules/backlogs/app/controllers/rb_application_controller.rb @@ -32,10 +32,6 @@ class RbApplicationController < ApplicationController before_action :load_sprint_and_project, :check_if_plugin_is_configured, :authorize - # Use special backlogs layout to initialize stimulus side-loading legacy backlogs scripts - # and CSS from frontend - layout "backlogs" - private # Loads the project to be used by the authorize filter to determine if diff --git a/modules/backlogs/app/controllers/rb_master_backlogs_controller.rb b/modules/backlogs/app/controllers/rb_master_backlogs_controller.rb index 0602221ec5d..9ee8e288269 100644 --- a/modules/backlogs/app/controllers/rb_master_backlogs_controller.rb +++ b/modules/backlogs/app/controllers/rb_master_backlogs_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH @@ -27,12 +29,29 @@ #++ class RbMasterBacklogsController < RbApplicationController + include WorkPackages::WithSplitView + menu_item :backlogs def index @owner_backlogs = Backlog.owner_backlogs(@project) @sprint_backlogs = Backlog.sprint_backlogs(@project) - - @last_update = (@sprint_backlogs + @owner_backlogs).filter_map(&:updated_at).max end + + def split_view + @owner_backlogs = Backlog.owner_backlogs(@project) + @sprint_backlogs = Backlog.sprint_backlogs(@project) + + respond_to do |format| + format.html do + if turbo_frame_request? + render "work_packages/split_view", layout: false + else + render :index + end + end + end + end + + def split_view_base_route = backlogs_project_backlogs_path(request.query_parameters) end diff --git a/modules/backlogs/app/controllers/rb_sprints_controller.rb b/modules/backlogs/app/controllers/rb_sprints_controller.rb index 3603a756b44..6abdcee3e02 100644 --- a/modules/backlogs/app/controllers/rb_sprints_controller.rb +++ b/modules/backlogs/app/controllers/rb_sprints_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH @@ -26,20 +28,66 @@ # See COPYRIGHT and LICENSE files for more details. #++ -# Responsible for exposing sprint CRUD. It SHOULD NOT be used for displaying the -# taskboard since the taskboard is a management interface used for managing -# objects within a sprint. For info about the taskboard, see -# RbTaskboardsController class RbSprintsController < RbApplicationController - def update - result = @sprint.update(params.permit(:name, - :start_date, - :effective_date)) - status = (result ? 200 : 400) + include OpTurbo::ComponentStream - respond_to do |format| - format.html { render partial: "sprint", status:, object: @sprint } + def edit_name + @backlog = Backlog.for(sprint: @sprint, project: @project) + + update_via_turbo_stream( + component: Backlogs::BacklogHeaderComponent.new( + backlog: @backlog, + project: @project, + state: :edit + ) + ) + + respond_with_turbo_streams + end + + def show_name + @backlog = Backlog.for(sprint: @sprint, project: @project) + + update_via_turbo_stream( + component: Backlogs::BacklogHeaderComponent.new( + backlog: @backlog, + project: @project, + state: :show + ) + ) + + respond_with_turbo_streams + end + + def update + call = Versions::UpdateService + .new(user: current_user, model: @sprint) + .call(attributes: sprint_params) + + if call.success? + status = 200 + state = :show + + @sprint = call.result + + render_success_flash_message_via_turbo_stream(message: I18n.t(:notice_successful_update)) + else + status = 422 + state = :edit + + render_error_flash_message_via_turbo_stream(message: I18n.t(:notice_unsuccessful_update_with_reason, reason: call.message)) end + + @backlog = Backlog.for(sprint: @sprint, project: @project) + + update_via_turbo_stream( + component: Backlogs::BacklogHeaderComponent.new( + backlog: @backlog, + project: @project, + state: + ) + ) + respond_with_turbo_streams(status:) end # Overwrite load_sprint_and_project to load the sprint from the :id instead of @@ -52,4 +100,10 @@ class RbSprintsController < RbApplicationController # This overrides sprint's project if we set another project, say a subproject @project = Project.find(params[:project_id]) if params[:project_id] end + + private + + def sprint_params + params.expect(sprint: %i[name start_date effective_date]) + end end diff --git a/modules/backlogs/app/controllers/rb_stories_controller.rb b/modules/backlogs/app/controllers/rb_stories_controller.rb index 3b6dbc3402a..76439dd4184 100644 --- a/modules/backlogs/app/controllers/rb_stories_controller.rb +++ b/modules/backlogs/app/controllers/rb_stories_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH @@ -27,12 +29,14 @@ #++ class RbStoriesController < RbApplicationController + include OpTurbo::ComponentStream + # This is a constant here because we will recruit it elsewhere to whitelist # attributes. This is necessary for now as we still directly use `attributes=` # in non-controller code. PERMITTED_PARAMS = %i[id status_id version_id story_points type_id subject author_id - sprint_id] + sprint_id].freeze def create call = Stories::CreateService @@ -59,15 +63,71 @@ class RbStoriesController < RbApplicationController respond_with_story(call) end + def move + story = Story.find(params[:id]) + + # call = Stories::UpdateService + # .new(user: current_user, story:) + # .call(attributes: move_params) + + if story.update(version_id: move_params[:target_id], **move_params.except(:target_id)) + render_success_flash_message_via_turbo_stream( + message: I18n.t(:enumeration_caption_order_changed) + ) + else + render_error_flash_message_via_turbo_stream( + message: I18n.t(:enumeration_could_not_be_moved) + call.errors.full_messages.to_sentence + ) + end + + backlog = Backlog.for(sprint: @sprint, project: @project) + replace_via_turbo_stream(component: Backlogs::BacklogComponent.new(backlog:, project: @project)) + + if story.saved_change_to_version_id? + new_sprint = story.version.becomes(Sprint) + new_backlog = Backlog.for(sprint: new_sprint, project: @project) + replace_via_turbo_stream(component: Backlogs::BacklogComponent.new(backlog: new_backlog, project: @project)) + end + + respond_with_turbo_streams + end + + def reorder + story = Story.find(params[:id]) + + if story.update(move_to: reorder_param) + render_success_flash_message_via_turbo_stream( + message: I18n.t(:enumeration_caption_order_changed) + ) + else + render_error_flash_message_via_turbo_stream( + message: I18n.t(:enumeration_could_not_be_moved) + call.errors.full_messages.to_sentence + ) + end + + backlog = Backlog.for(sprint: @sprint, project: @project) + + replace_via_turbo_stream(component: Backlogs::BacklogComponent.new(backlog:, project: @project)) + + respond_with_turbo_streams + end + private def respond_with_story(call) status = call.success? ? 200 : 400 story = call.result - respond_to do |format| - format.html { render partial: "story", object: story, status:, locals: { errors: call.errors } } - end + respond_with_turbo_streams + end + + def move_params + params.require(%i[position target_id]) + params.permit(:position, :target_id) + end + + def reorder_param + params.expect(:direction) end def story_params diff --git a/modules/backlogs/app/forms/backlogs/backlog_header_form.rb b/modules/backlogs/app/forms/backlogs/backlog_header_form.rb new file mode 100644 index 00000000000..0a258dd8dd7 --- /dev/null +++ b/modules/backlogs/app/forms/backlogs/backlog_header_form.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module Backlogs + class BacklogHeaderForm < ApplicationForm + attr_reader :cancel_path + + form do |f| + f.group(layout: :horizontal) do |group| + group.text_field( + name: :name, + label: attribute_name(:name), + placeholder: attribute_name(:name), + visually_hide_label: true, + autofocus: true, + autocomplete: "off" + ) + + group.single_date_picker( + name: :start_date, + label: attribute_name(:start_date), + placeholder: attribute_name(:start_date), + visually_hide_label: true, + leading_visual: { icon: :calendar }, + datepicker_options: {} + ) + group.single_date_picker( + name: :effective_date, + label: attribute_name(:effective_date), + placeholder: attribute_name(:effective_date), + visually_hide_label: true, + leading_visual: { icon: :calendar }, + datepicker_options: {} + ) + + group.submit(scheme: :primary, name: :submit, label: I18n.t(:button_save)) + group.button( + scheme: :secondary, + name: :cancel, + label: I18n.t(:button_cancel), + tag: :a, + data: { turbo_stream: true }, + href: cancel_path + ) + end + end + + def initialize(cancel_path:) + super() + + @cancel_path = cancel_path + end + end +end diff --git a/modules/backlogs/app/helpers/burndown_charts_helper.rb b/modules/backlogs/app/helpers/burndown_charts_helper.rb index 03c031eab72..1c087f11125 100644 --- a/modules/backlogs/app/helpers/burndown_charts_helper.rb +++ b/modules/backlogs/app/helpers/burndown_charts_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH @@ -27,52 +29,23 @@ #++ module BurndownChartsHelper - def yaxis_labels(burndown) - max = burndown.max[:points] - - mvalue = (max / 25) + 1 - - labels = (0..mvalue).map { |i| "[#{i * 25}, #{i * 25}]" } - - mvalue = mvalue + 1 if mvalue == 1 || ((max % 25) == 0) - - labels << "[#{mvalue * 25}, '#{I18n.t('backlogs.points')}']" - - result = labels.join(", ") - - result.html_safe - end - def xaxis_labels(burndown) # 14 entries (plus the axis label) have come along as the best value for a good optical result. # Thus it is enough space between the entries. entries_displayed = (burndown.days.length / 14.0).ceil - result = burndown.days.enum_for(:each_with_index).map do |d, i| + burndown.days.enum_for(:each_with_index).map do |d, i| if (i % entries_displayed) == 0 - "[#{i + 1}, '#{escape_javascript(::I18n.t('date.abbr_day_names')[d.wday % 7])} #{d.strftime('%d/%m')}']" + ["#{escape_javascript(::I18n.t('date.abbr_day_names')[d.wday % 7])} #{d.strftime('%d/%m')}"] end - end.join(",").html_safe + - ", [#{burndown.days.length + 1}, - '#{I18n.t('backlogs.date')}']".html_safe + end end def dataseries(burndown) - dataset = {} - burndown.series.each do |s| - dataset[s.first] = { - label: I18n.t("backlogs." + s.first.to_s), - data: s.last.enum_for(:each_with_index).map { |val, i| [i + 1, val] } + burndown.series.map do |s| + { + label: I18n.t("burndown.#{s.first}"), + data: s.last.enum_for(:each) } end - - dataset - end - - def burndown_series_checkboxes(burndown) - boxes = "" - burndown.series(:all).map { |s| s.first.to_s }.sort.each do |series| - boxes += "#{I18n.t('backlogs.' + series.to_s)}
" - end - boxes.html_safe end end diff --git a/modules/backlogs/app/helpers/rb_common_helper.rb b/modules/backlogs/app/helpers/rb_common_helper.rb index d25776d4fe7..c59c635c974 100644 --- a/modules/backlogs/app/helpers/rb_common_helper.rb +++ b/modules/backlogs/app/helpers/rb_common_helper.rb @@ -27,6 +27,12 @@ #++ module RbCommonHelper + def format_date_range(dates) + dates + .map { |date| tag.time(datetime: date.iso8601) { format_date(date) } } + .then { |dates| safe_join(dates, " – ") } + end + def assignee_id_or_empty(story) story.assigned_to_id.to_s end @@ -57,14 +63,6 @@ module RbCommonHelper end end - # Return true if the difference between two colors - # matches the W3C recommendations for readability - # See http://www.wat-c.org/tools/CCA/1.1/ - def colors_diff_ok?(color_1, color_2) - cont, bright = find_color_diff color_1, color_2 - (cont > 500) && (bright > 125) # Acceptable diff according to w3c - end - def color_contrast(color) _, bright = find_color_diff 0x000000, color (bright > 128) @@ -95,18 +93,13 @@ module RbCommonHelper def background_color_hex(task) background_color = get_backlogs_preference(task.assigned_to, :task_color) - background_color_hex = background_color.sub("#", "0x").hex + background_color.sub("#", "0x").hex end def id_or_empty(item) item.id.to_s end - def shortened_id(record) - id = record.id.to_s - (id.length > 8 ? "#{id[0..1]}...#{id[-4..-1]}" : id) - end - def work_package_link_or_empty(work_package) modal_link_to_work_package(work_package.id, work_package, class: "prevent_edit") unless work_package.new_record? end @@ -120,53 +113,14 @@ module RbCommonHelper link_to(title, path, options.merge(id: html_id, target: "_blank")) end - def sprint_link_or_empty(item) - item_id = item.id.to_s - text = (item_id.length > 8 ? "#{item_id[0..1]}...#{item_id[-4..-1]}" : item_id) - if item.new_record? - "" - else - link_to(text, backlogs_project_sprint_path(id: item.id, project_id: item.project.identifier), class: "prevent_edit") - end - end - def mark_if_closed(story) !story.new_record? && work_package_status_for_id(story.status_id).is_closed? ? "closed" : "" end - def story_points_or_empty(story) - story.story_points.to_s - end - - def status_id_or_default(story) - story.new_record? ? new_record_status.id : story.status_id - end - - def status_label_or_default(story) - story.new_record? ? new_record_status.name : h(work_package_status_for_id(story.status_id).name) - end - - def sprint_html_id_or_empty(sprint) - sprint.id.nil? ? "" : "sprint_#{sprint.id}" - end - def story_html_id_or_empty(story) story.id.nil? ? "" : "story_#{story.id}" end - def type_id_or_empty(story) - story.type_id.to_s - end - - def type_name_or_empty(story) - return "" if story.type_id.nil? - - type = backlogs_types_by_id[story.type_id] - return "" if type.nil? - - h(type.name) - end - def date_string_with_milliseconds(d, add = 0) return "" if d.blank? @@ -177,49 +131,8 @@ module RbCommonHelper item.remaining_hours.blank? || item.remaining_hours == 0 ? "" : item.remaining_hours end - def available_story_types - @available_story_types ||= begin - types = story_types & @project.types if @project - - types - end - end - - def available_statuses_by_type - @available_statuses_by_type ||= begin - available_statuses_by_type = Hash.new do |type_hash, type| - type_hash[type] = Hash.new do |status_hash, status| - status_hash[status] = [status] - end - end - - all_workflows.each do |w| - type_status = available_statuses_by_type[story_types_by_id[w.type_id]][w.old_status] - - type_status << w.new_status unless type_status.include?(w.new_status) - end - - available_statuses_by_type - end - end - - def show_burndown_link(project, sprint) - link_to(I18n.t("backlogs.show_burndown_chart"), - backlogs_project_sprint_burndown_chart_path(project.identifier, sprint), - class: "show_burndown_chart button", - target: :_blank, rel: :noopener) - end - private - def new_record_status - @new_record_status ||= all_work_package_status.first - end - - def default_work_package_status - @default_work_package_status ||= all_work_package_status.detect(&:is_default) - end - def work_package_status_for_id(id) @all_work_package_status_by_id ||= all_work_package_status.inject({}) do |mem, status| mem[status.id] = status @@ -254,13 +167,6 @@ module RbCommonHelper end end - def backlogs_types_by_id - @backlogs_types_by_id ||= backlogs_types.inject({}) do |mem, type| - mem[type.id] = type - mem - end - end - def story_types @story_types ||= begin backlogs_type_ids = Setting.plugin_openproject_backlogs["story_types"].map(&:to_i) @@ -269,20 +175,7 @@ module RbCommonHelper end end - def story_types_by_id - @story_types_by_id ||= story_types.inject({}) do |mem, type| - mem[type.id] = type - mem - end - end - def get_backlogs_preference(assignee, attr) assignee.is_a?(User) ? assignee.backlogs_preference(attr) : "#24B3E7" end - - def template_story - Story.new.tap do |s| - s.type = available_story_types.first - end - end end diff --git a/modules/backlogs/app/helpers/rb_master_backlogs_helper.rb b/modules/backlogs/app/helpers/rb_master_backlogs_helper.rb deleted file mode 100644 index 89866fd1cff..00000000000 --- a/modules/backlogs/app/helpers/rb_master_backlogs_helper.rb +++ /dev/null @@ -1,121 +0,0 @@ -#-- copyright -# OpenProject is an open source project management software. -# Copyright (C) the OpenProject GmbH -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License version 3. -# -# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: -# Copyright (C) 2006-2013 Jean-Philippe Lang -# Copyright (C) 2010-2013 the ChiliProject Team -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# See COPYRIGHT and LICENSE files for more details. -#++ - -module RbMasterBacklogsHelper - include Redmine::I18n - - def render_backlog_menu(backlog) - # associated javascript defined in taskboard.js - content_tag(:div, class: "backlog-menu") do - [ - content_tag(:div, "", class: "menu-trigger icon-context icon-pulldown icon-small"), - content_tag(:ul, class: "items") do - backlog_menu_items_for(backlog).map do |item| - content_tag(:li, item, class: "item") - end.join.html_safe - end - ].join.html_safe - end - end - - def backlog_menu_items_for(backlog) - items = common_backlog_menu_items_for(backlog) - - if backlog.sprint_backlog? - items.merge!(sprint_backlog_menu_items_for(backlog)) - end - - menu = [] - %i[new_story stories_tasks task_board burndown cards wiki configs properties].each do |key| - menu << items[key] if items.keys.include?(key) - end - - menu - end - - def common_backlog_menu_items_for(backlog) - items = {} - - if current_user.allowed_in_project?(:add_work_packages, @project) - items[:new_story] = content_tag(:a, - I18n.t("backlogs.add_new_story"), - href: "#", - class: "add_new_story") - end - - items[:stories_tasks] = link_to(I18n.t(:label_stories_tasks), - controller: "/rb_queries", - action: "show", - project_id: @project, - sprint_id: backlog.sprint) - - if current_user.allowed_in_project?(:manage_versions, @project) - items[:properties] = properties_link(backlog) - end - - items - end - - def properties_link(backlog) - back_path = backlogs_project_backlogs_path(@project) - - version_path = edit_version_path(backlog.sprint, back_url: back_path, project_id: @project.id) - - link_to(I18n.t(:"backlogs.properties"), version_path) - end - - def sprint_backlog_menu_items_for(backlog) - items = {} - - if current_user.allowed_in_project?(:view_taskboards, @project) - items[:task_board] = link_to(I18n.t(:label_task_board), - { controller: "/rb_taskboards", - action: "show", - project_id: @project, - sprint_id: backlog.sprint }, - class: "show_task_board") - end - - if backlog.sprint.has_burndown? - items[:burndown] = content_tag(:a, - I18n.t("backlogs.show_burndown_chart"), - href: "#", - class: "show_burndown_chart") - end - - if @project.module_enabled? "wiki" - items[:wiki] = link_to(I18n.t(:label_wiki), - controller: "/rb_wikis", - action: "edit", - project_id: @project, - sprint_id: backlog.sprint) - end - - items - end -end diff --git a/modules/backlogs/app/models/backlog.rb b/modules/backlogs/app/models/backlog.rb index 9dd2cbbaa89..482106b803c 100644 --- a/modules/backlogs/app/models/backlog.rb +++ b/modules/backlogs/app/models/backlog.rb @@ -27,11 +27,18 @@ #++ class Backlog + extend ActiveModel::Naming + attr_accessor :sprint, :stories - def self.owner_backlogs(project, options = {}) - options.reverse_merge!(limit: nil) + delegate :id, to: :sprint, prefix: true + def self.for(sprint:, project:) + owner_backlog = sprint.settings(project)&.display == VersionSetting::DISPLAY_RIGHT + new(sprint:, stories: sprint.stories(project), owner_backlog:) + end + + def self.owner_backlogs(project) backlogs = Sprint.apply_to(project).with_status_open.displayed_right(project).order(:name) stories_by_sprints = Story.backlogs(project.id, backlogs.map(&:id)) @@ -47,11 +54,10 @@ class Backlog sprints.map { |sprint| new(stories: stories_by_sprints[sprint.id], sprint:) } end - def initialize(options = {}) - options = options.with_indifferent_access - @sprint = options["sprint"] - @stories = options["stories"] - @owner_backlog = options["owner_backlog"] + def initialize(sprint:, stories:, owner_backlog: false) + @sprint = sprint + @stories = stories + @owner_backlog = owner_backlog end def updated_at @@ -65,4 +71,8 @@ class Backlog def sprint_backlog? !owner_backlog? end + + def to_key + [sprint_id] + end end diff --git a/modules/backlogs/app/models/sprint.rb b/modules/backlogs/app/models/sprint.rb index 5cdb1f0f245..cdb61d41a8d 100644 --- a/modules/backlogs/app/models/sprint.rb +++ b/modules/backlogs/app/models/sprint.rb @@ -156,6 +156,10 @@ class Sprint < Version Impediment.default_scope.where(version_id: self, project_id: project) end + def settings(project) + version_settings.find { it.project_id == project.id || it.project_id.nil? } + end + private def create_wiki_page(page_title, author: User.current) diff --git a/modules/backlogs/app/views/rb_burndown_charts/_burndown.html.erb b/modules/backlogs/app/views/rb_burndown_charts/_burndown.html.erb index 5dc27ca0548..487f17ba88a 100644 --- a/modules/backlogs/app/views/rb_burndown_charts/_burndown.html.erb +++ b/modules/backlogs/app/views/rb_burndown_charts/_burndown.html.erb @@ -27,112 +27,7 @@ See COPYRIGHT and LICENSE files for more details. ++#%> -<%= nonced_javascript_tag do %> - jQuery(function () { - var Burndown = { - datasets: <%= dataseries(burndown).to_json.html_safe %> , - previousPoint: null, - - setDatasetColor: function () { - var i = 0; - - jQuery.each(Burndown.datasets, function (key, val) { - val.color = i; - val.points = {show: false, radius: 2}; - val.lines = {show: true}; - ++i; - }); - }, - - plotAccordingToChoices: function () { - var data = []; - - jQuery('.burndown_control').find("input:checked").each(function () { - var key = jQuery(this).attr('value'); - - if (key && Burndown.datasets[key]) { - data.push(Burndown.datasets[key]); - } - }); - - if (data.length === 0) { //in order to render an empty graph if no data is selected - data.push({data : []}); - } - - Burndown.plot(data); - }, - - plot: function (data) { - if (data.length > 0) { - jQuery.plot(jQuery(".burndown_chart"), data, { - yaxis: { min: 0, - ticks: [ <%= yaxis_labels(burndown) %> ] }, - xaxis: { - ticks: [ <%= xaxis_labels(burndown) %> ], - tickDecimals: 0, - max: <%= burndown.days.length + 1 %>, - min: 1 - }, - grid: { hoverable: true, clickable: true } - }); - } - }, - - showTooltip: function(x, y, contents) { - jQuery('
' + contents + '
').css( { - position: 'absolute', - display: 'none', - top: y + 5, - left: x + 5, - border: '1px solid #fdd', - padding: '2px', - 'background-color': '#fee', - opacity: 0.80 - }).appendTo("body").css('z-index', 2000).fadeIn(200); - }, - - showTooltipOnHover: function (event, pos, item) { - - if (item) { - if (Burndown.previousPoint != item.dataIndex) { - Burndown.previousPoint = item.dataIndex; - - jQuery("#tooltip").remove(); - var x = item.datapoint[0].toFixed(0), - y = item.datapoint[1].toFixed(0); - - Burndown.showTooltip(item.pageX, item.pageY, - item.series.label + ": " + y); - } - } - else { - jQuery("#tooltip").remove(); - Burndown.previousPoint = null; - } - }, - - init: function () { - Burndown.setDatasetColor(); - - jQuery('.burndown_control input').click(Burndown.plotAccordingToChoices); - jQuery(".burndown_chart").bind("plothover", Burndown.showTooltipOnHover); - - Burndown.plotAccordingToChoices(); - }, - - saveInit: function() { - // Ensure jQuery.plot is defined before progressing. - // This static page might already be ready but the webpack required - // jquery.flot.js file might not be loaded yet. - - if (jQuery.plot) { - this.init(); - } else { - setTimeout(() => { this.saveInit()}, 50); - } - } - }; - - Burndown.saveInit(); - }); -<% end %> +<%= angular_component_tag "opce-burndown-chart", "chart-data": { + labels: xaxis_labels(@burndown), + datasets: dataseries(@burndown) + }.to_json %> diff --git a/modules/backlogs/app/views/rb_burndown_charts/show.html.erb b/modules/backlogs/app/views/rb_burndown_charts/show.html.erb index 3282a40457d..dfc5f70dd02 100644 --- a/modules/backlogs/app/views/rb_burndown_charts/show.html.erb +++ b/modules/backlogs/app/views/rb_burndown_charts/show.html.erb @@ -27,19 +27,18 @@ See COPYRIGHT and LICENSE files for more details. ++#%> -

- <%= "#{@sprint.name}: #{@sprint.start_date.present? ? I18n.l(@sprint.start_date) : ''} - #{@sprint.effective_date.present? ? I18n.l(@sprint.effective_date) : ''}" %> -

+<% html_title @sprint.name %> + +<%= render(Backlogs::SprintPageHeaderComponent.new(sprint: @sprint, project: @project)) %> <% if @burndown %> <%= render partial: "burndown", locals: { div: "burndown_", burndown: @burndown } %> - -
<%= t("backlogs.generating_chart") %>
- -
- <%= t("backlogs.chart_options") %> - <%= burndown_series_checkboxes(@burndown) %> -
<% else %> - <%= t("backlogs.no_burndown_data") %> + <%= + render(Primer::Beta::Blankslate.new(border: true, spacious: true)) do |blankslate| + blankslate.with_visual_icon(icon: :graph) + blankslate.with_heading(tag: :h2).with_content(t(".blankslate_title")) + blankslate.with_description_content(t(".blankslate_description")) + end + %> <% end %> diff --git a/modules/backlogs/app/views/rb_master_backlogs/index.html.erb b/modules/backlogs/app/views/rb_master_backlogs/index.html.erb index 0ecc2ab079a..c4e99028ee1 100644 --- a/modules/backlogs/app/views/rb_master_backlogs/index.html.erb +++ b/modules/backlogs/app/views/rb_master_backlogs/index.html.erb @@ -27,50 +27,61 @@ See COPYRIGHT and LICENSE files for more details. ++#%> -
<% html_title t(:label_backlogs) %> -<%= - render Primer::OpenProject::PageHeader.new do |header| - header.with_title { t(:label_backlogs) } - header.with_breadcrumbs( - [{ href: project_overview_path(@project.id), text: @project.name }, - t(:label_backlogs)] - ) - end -%> +<% content_for :content_header do %> + <%= + render Primer::OpenProject::PageHeader.new do |header| + header.with_title { t(:label_backlogs) } + header.with_breadcrumbs( + [{ href: project_overview_path(@project.id), text: @project.name }, + t(:label_backlogs)] + ) + end + %> -<%= render(Primer::OpenProject::SubHeader.new) do |subheader| - subheader.with_action_button( - scheme: :primary, - leading_icon: :plus, - label: I18n.t(:label_version_new), - tag: :a, - href: url_for({ controller: "/versions", action: "new", project_id: @project }) - ) do - t("activerecord.models.version") - end - end %> - -<% if (@owner_backlogs.empty? && @sprint_backlogs.empty?) %> - <%= no_results_box action_url: new_project_version_path(@project), - display_action: authorize_for("versions", "new"), - custom_title: t(:backlogs_empty_title), - custom_action_text: t(:backlogs_empty_action_text) %> + <%= render(Primer::OpenProject::SubHeader.new) do |subheader| + subheader.with_action_button( + scheme: :primary, + leading_icon: :plus, + label: I18n.t(:label_version_new), + tag: :a, + href: url_for({ controller: "/versions", action: "new", project_id: @project }) + ) do + t("activerecord.models.version") + end + end %> <% end %> -
-
-
- <%= render partial: "backlog", collection: @owner_backlogs %> -
-
- <%= render partial: "backlog", collection: @sprint_backlogs %> -
-
+<% content_for :content_body do %> + <% if (@owner_backlogs.empty? && @sprint_backlogs.empty?) %> + <%= + render(Primer::Beta::Blankslate.new(border: true, spacious: true)) do |blankslate| + blankslate.with_visual_icon(icon: :versions) + blankslate.with_heading(tag: :h2).with_content(t(:backlogs_empty_title)) -
- <%= render partial: "rb_stories/helpers" %> -
<%= date_string_with_milliseconds(@last_update, 0.001) %>
+ if current_user.allowed_in_project?(:manage_versions, @project) + blankslate.with_description_content(t(:backlogs_empty_action_text)) + end + end + %> + <% end %> + +
+
+ +
+ <%= render(Backlogs::BacklogComponent.with_collection(@sprint_backlogs, project: @project)) %> +
+
+ <%= render(Backlogs::BacklogComponent.with_collection(@owner_backlogs, project: @project)) %> +
+
-
+<% end %> + +<% content_for :content_body_right do %> + <%= render(split_view_instance) if render_work_package_split_view? %> +<% end %> diff --git a/modules/backlogs/app/views/rb_sprints/_sprint.html.erb b/modules/backlogs/app/views/rb_sprints/_sprint.html.erb deleted file mode 100644 index af87f44bda9..00000000000 --- a/modules/backlogs/app/views/rb_sprints/_sprint.html.erb +++ /dev/null @@ -1,78 +0,0 @@ -<%#-- copyright -OpenProject is an open source project management software. -Copyright (C) the OpenProject GmbH - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License version 3. - -OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: -Copyright (C) 2006-2013 Jean-Philippe Lang -Copyright (C) 2010-2013 the ChiliProject Team - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -See COPYRIGHT and LICENSE files for more details. - -++#%> - -
-
-
<%= sprint_link_or_empty(sprint) %>
-
<%= id_or_empty(sprint) %>
-
- - <% editable = User.current.allowed_in_project?(:update_sprints, @project) ? "editable" : "" %> - -
-
<%= sprint.effective_date %>
-
<%= sprint.start_date %>
-
<%= sprint.name %>
-
- - <% if User.current.allowed_in_project?(:update_sprints, @project) %> -
- <%= angular_component_tag "opce-basic-single-date-picker", - inputs: { - value: sprint.effective_date, - inputClassNames: "effective_date editor", - id: "effective_date_#{sprint.id}", - name: :effective_date - } %> - <%= angular_component_tag "opce-basic-single-date-picker", - inputs: { - value: sprint.start_date, - inputClassNames: "start_date editor", - id: "start_date_#{sprint.id}", - name: :start_date - } %> - <%= text_field_tag :name, - sprint.name, - class: "name editor" %> -
- <% end %> - -
- <%= render partial: "shared/model_errors", object: sprint.errors %> -
-
diff --git a/modules/backlogs/app/views/rb_stories/_helpers.html.erb b/modules/backlogs/app/views/rb_stories/_helpers.html.erb deleted file mode 100644 index 34816d40855..00000000000 --- a/modules/backlogs/app/views/rb_stories/_helpers.html.erb +++ /dev/null @@ -1,75 +0,0 @@ -<%#-- copyright -OpenProject is an open source project management software. -Copyright (C) the OpenProject GmbH - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License version 3. - -OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: -Copyright (C) 2006-2013 Jean-Philippe Lang -Copyright (C) 2010-2013 the ChiliProject Team - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -See COPYRIGHT and LICENSE files for more details. - -++#%> - - -<% available_statuses_by_type.each do |type, statuses| %> - <% statuses.each do |old_status, allowed_statuses| %> - - <% end %> -<% end %> - -<% all_work_package_status.each do |old_status| %> - -<% end %> - - - - - -
- <%= render partial: "rb_stories/story", object: template_story, locals: { project: @project, permission: :add_work_packages } %> -
diff --git a/modules/backlogs/app/views/rb_stories/_story.html.erb b/modules/backlogs/app/views/rb_stories/_story.html.erb deleted file mode 100644 index 0f791ab2aac..00000000000 --- a/modules/backlogs/app/views/rb_stories/_story.html.erb +++ /dev/null @@ -1,79 +0,0 @@ -<%#-- copyright -OpenProject is an open source project management software. -Copyright (C) the OpenProject GmbH - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License version 3. - -OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: -Copyright (C) 2006-2013 Jean-Philippe Lang -Copyright (C) 2010-2013 the ChiliProject Team - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -See COPYRIGHT and LICENSE files for more details. - -++#%> - -<% - project ||= story.project - permission ||= :edit_work_packages - other_fields_editable = User.current.allowed_in_project?(permission, project) - status_field_editable = other_fields_editable || User.current.allowed_in_project?(:change_work_package_status, project) -%> -
  • " id="<%= story_html_id_or_empty(story) %>"> -
    -
    <%= work_package_link_or_empty(story) %>
    -
    <%= id_or_empty(story) %>
    -
    -
    <%= story_points_or_empty(story) %>
    -
    -
    <%= status_label_or_default(story) %>
    -
    <%= status_id_or_default(story) %>
    -
    -
    -
    <%= type_name_or_empty(story) %>:
    -
    <%= type_id_or_empty(story) %>
    -
    -
    <%= story.subject %>
    -
    <%= story.version_id %>
    -
    <%= !defined?(higher_item) || higher_item.nil? ? "" : higher_item.id %>
    -
    - <%= render(partial: "shared/model_errors", object: errors) if defined?(errors) && !errors.empty? %> -
    -
  • diff --git a/modules/backlogs/app/views/rb_taskboards/show.html.erb b/modules/backlogs/app/views/rb_taskboards/show.html.erb index 7416de928d2..acae533782b 100644 --- a/modules/backlogs/app/views/rb_taskboards/show.html.erb +++ b/modules/backlogs/app/views/rb_taskboards/show.html.erb @@ -27,33 +27,36 @@ See COPYRIGHT and LICENSE files for more details. ++#%> +<% content_for :additional_js_dom_ready do %> + <%= render(partial: "shared/server_variables", formats: [:js]) %> +<% end %> + +<% content_controller "backlogs--taskboard-legacy" %> + <% html_title @sprint.name %> -<%= - render Primer::OpenProject::PageHeader.new do |header| - header.with_title { @sprint.name } - header.with_breadcrumbs( - [{ href: project_overview_path(@project.id), text: @project.name }, - { href: backlogs_project_backlogs_path(@project), text: t(:label_backlogs) }, - @sprint.name] - ) - end -%> -<%# we decided to keep current toolbar design for taskboard %> -
    -
    -
  • -
    - -
    - -
  • - <% if @sprint.has_burndown? %> -
  • - <%= show_burndown_link(@project, @sprint) %> -
  • + +<%= render(Backlogs::SprintPageHeaderComponent.new(sprint: @sprint, project: @project)) %> + +<%= render(Primer::OpenProject::SubHeader.new) do |component| %> + <% component.with_filter_component(id: "col_width") do %> + <%= + render( + Primer::Alpha::TextField.new( + name: :col_width_input, + type: :number, + label: t(:"backlogs.column_width"), + placeholder: t(:"backlogs.column_width"), + visually_hide_label: true, + leading_visual: { icon: :"zoom-in" }, + step: 1, + input_width: :xsmall, + autocomplete: "off" + ) + ) + %> <% end %> -
    -
    +<% end %> +
    diff --git a/modules/backlogs/app/views/shared/_server_variables.js.erb b/modules/backlogs/app/views/shared/_server_variables.js.erb index d03965ee336..c50138cfda9 100644 --- a/modules/backlogs/app/views/shared/_server_variables.js.erb +++ b/modules/backlogs/app/views/shared/_server_variables.js.erb @@ -36,11 +36,6 @@ RB.constants = { sprint_id: <%= @sprint ? @sprint.id : "null" %> }; -RB.i18n = { - generating_graph: '<%= j I18n.t("backlogs.generating_chart").html_safe %>', - burndown_graph: '<%= j I18n.t("backlogs.burndown_graph").html_safe %>' -}; - RB.urlFor = (function () { const routes = { update_sprint: '<%= backlogs_project_sprint_path(project_id: @project.identifier, id: ":id") %>', @@ -52,9 +47,7 @@ RB.urlFor = (function () { update_task: '<%= backlogs_project_sprint_task_path(project_id: @project.identifier, sprint_id: ":sprint_id", id: ":id") %>', create_impediment: '<%= backlogs_project_sprint_impediments_path(project_id: @project.identifier, sprint_id: ":sprint_id") %>', - update_impediment: '<%= backlogs_project_sprint_impediment_path(project_id: @project.identifier, sprint_id: ":sprint_id", id: ":id") %>', - - show_burndown_chart: '<%= backlogs_project_sprint_burndown_chart_path(project_id: @project.identifier, sprint_id: ":sprint_id") %>' + update_impediment: '<%= backlogs_project_sprint_impediment_path(project_id: @project.identifier, sprint_id: ":sprint_id", id: ":id") %>' }; return function (routeName, options) { diff --git a/modules/backlogs/config/locales/en.yml b/modules/backlogs/config/locales/en.yml index 17a4515f2ff..d6529e816a5 100644 --- a/modules/backlogs/config/locales/en.yml +++ b/modules/backlogs/config/locales/en.yml @@ -34,6 +34,8 @@ en: activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Position" story_points: "Story Points" @@ -55,14 +57,14 @@ en: task_type: "Task type" backlogs: - add_new_story: "New Story" + add_new_story: "New story" any: "any" backlog_settings: "Backlogs settings" burndown_graph: "Burndown Graph" card_paper_size: "Paper size for card printing" chart_options: "Chart options" close: "Close" - column_width: "Column width:" + column_width: "Column width" date: "Day" definition_of_done: "Definition of Done" generating_chart: "Generating Graph..." @@ -72,8 +74,9 @@ en: caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." work_package_is_closed: "Work package is done, when" label_is_done_status: "Status %{status_name} means done" - no_burndown_data: "No burndown data available. It is necessary to have the sprint start- and end dates set." - points: "Points" + points: + one: "%{count} point" + other: "%{count} points" positions_could_not_be_rebuilt: "Positions could not be rebuilt." positions_rebuilt_successfully: "Positions rebuilt successfully." properties: "Properties" @@ -83,8 +86,9 @@ en: remaining_hours_ideal: "Remaining work (ideal)" show_burndown_chart: "Burndown Chart" story: "Story" - story_points: "Story Points" - story_points_ideal: "Story Points (ideal)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "Task" task_color: "Task color" unassigned: "Unassigned" @@ -93,12 +97,33 @@ en: button_update_backlogs: "Update backlogs module" x_more: "%{count} more..." + backlog_component: + sprint_backlog: + blankslate_title: "Sprint Backlog is empty" + blankslate_description: "No items planned yet. Drag items here to add them to the Sprint." + product_backlog: + blankslate_title: "Product Backlog is empty" + blankslate_description: "There is no upcoming work defined in the Product Backlog" + + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + + story_component: + label_drag_story: "Move %{name}" + + story_menu_component: + label_actions: "Story actions" + backlogs_active: "active" backlogs_any: "any" backlogs_inactive: "Project shows no activity" backlogs_points_burn_direction: "Points burn up/down" backlogs_product_backlog: "Product backlog" - backlogs_product_backlog_is_empty: "Product backlog is empty" backlogs_product_backlog_unsized: "The top of the product backlog has unsized stories" backlogs_sizing_inconsistent: "Story sizes vary against their estimates" backlogs_sprint_notes_missing: "Closed sprints without retrospective/review notes" @@ -115,6 +140,9 @@ en: backlogs_empty_title: "No versions are defined to be used in backlogs" backlogs_empty_action_text: "To get started with backlogs, create a new version and assign it to a backlogs column." + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" button_edit_wiki: "Edit wiki page" @@ -180,6 +208,11 @@ en: project_module_backlogs: "Backlogs" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." + rb_label_copy_tasks: "Copy work packages" rb_label_copy_tasks_all: "All" rb_label_copy_tasks_none: "None" diff --git a/modules/backlogs/config/locales/js-en.yml b/modules/backlogs/config/locales/js-en.yml index de420c5e358..059e83f9b75 100644 --- a/modules/backlogs/config/locales/js-en.yml +++ b/modules/backlogs/config/locales/js-en.yml @@ -31,3 +31,6 @@ en: work_packages: properties: storyPoints: "Story Points" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/routes.rb b/modules/backlogs/config/routes.rb index e3a3bb29945..ee43551ae7d 100644 --- a/modules/backlogs/config/routes.rb +++ b/modules/backlogs/config/routes.rb @@ -27,11 +27,23 @@ #++ Rails.application.routes.draw do + concern :with_split_view do |options| + get "details/:work_package_id(/:tab)", + action: options.fetch(:action, :split_view), + defaults: { tab: :overview }, + as: :details, + work_package_split_view: true + end + scope "", as: "backlogs" do scope "projects/:project_id", as: "project" do - resources :backlogs, controller: :rb_master_backlogs, only: :index + resources :backlogs, controller: :rb_master_backlogs, only: :index do + collection do + concerns :with_split_view, base_route: :backlogs_project_backlogs_path + end + end - resources :sprints, controller: :rb_sprints, only: %i[show update] do + resources :sprints, controller: :rb_sprints, only: %i[update] do resource :query, controller: :rb_queries, only: :show resource :taskboard, controller: :rb_taskboards, only: :show @@ -44,7 +56,17 @@ Rails.application.routes.draw do resources :tasks, controller: :rb_tasks, only: %i[create update] - resources :stories, controller: :rb_stories, only: %i[create update] + resources :stories, controller: :rb_stories, only: %i[create update] do + member do + put :move + post :reorder + end + end + + member do + get :edit_name + get :show_name + end end resource :query, controller: :rb_queries, only: :show diff --git a/modules/backlogs/lib/open_project/backlogs/engine.rb b/modules/backlogs/lib/open_project/backlogs/engine.rb index 3d9ee5b8271..81308d00175 100644 --- a/modules/backlogs/lib/open_project/backlogs/engine.rb +++ b/modules/backlogs/lib/open_project/backlogs/engine.rb @@ -60,6 +60,8 @@ module OpenProject::Backlogs end OpenProject::AccessControl.permission(:edit_work_packages).tap do |edit| + edit.controller_actions << "rb_stories/move" + edit.controller_actions << "rb_stories/reorder" edit.controller_actions << "rb_stories/update" edit.controller_actions << "rb_tasks/update" edit.controller_actions << "rb_impediments/update" @@ -73,8 +75,8 @@ module OpenProject::Backlogs project_module :backlogs, dependencies: :work_package_tracking do # Master backlog permissions permission :view_master_backlog, - { rb_master_backlogs: :index, - rb_sprints: %i[index show], + { rb_master_backlogs: %i[index split_view], + rb_sprints: %i[index show show_name], rb_wikis: :show, rb_stories: %i[index show], rb_queries: :show, @@ -102,7 +104,7 @@ module OpenProject::Backlogs # :show_sprints and :list_sprints are implicit in :view_master_backlog permission permission :update_sprints, { - rb_sprints: %i[edit update], + rb_sprints: %i[edit_name update], rb_wikis: %i[edit update] }, permissible_on: :project, diff --git a/modules/backlogs/spec/components/backlogs/backlog_component_spec.rb b/modules/backlogs/spec/components/backlogs/backlog_component_spec.rb new file mode 100644 index 00000000000..ed362f8dae1 --- /dev/null +++ b/modules/backlogs/spec/components/backlogs/backlog_component_spec.rb @@ -0,0 +1,149 @@ +# 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 Backlogs::BacklogComponent, type: :component do + shared_let(:type_feature) { create(:type_feature) } + shared_let(:type_task) { create(:type_task) } + shared_let(:default_status) { create(:default_status) } + shared_let(:default_priority) { create(:default_priority) } + shared_let(:user) { create(:admin) } + current_user { user } + + let(:project) { create(:project, types: [type_feature, type_task]) } + let(:sprint) { create(:sprint, project:, name: "Sprint 1", start_date: Date.yesterday, effective_date: Date.tomorrow) } + let(:stories) { [] } + let(:backlog) { Backlog.new(sprint:, stories:) } + + before do + allow(Setting) + .to receive(:plugin_openproject_backlogs) + .and_return("story_types" => [type_feature.id.to_s], "task_type" => type_task.id.to_s) + + allow(user).to receive(:backlogs_preference).with(:versions_default_fold_state).and_return("open") + end + + def render_component + render_inline(described_class.new(backlog:, project:, current_user: user)) + end + + describe "rendering" do + context "with stories" do + let(:story1) do + create(:story, + project:, + type: type_feature, + status: default_status, + priority: default_priority, + story_points: 5, + position: 1, + version: sprint) + end + let(:story2) do + create(:story, + project:, + type: type_feature, + status: default_status, + priority: default_priority, + story_points: 3, + position: 2, + version: sprint) + end + let(:stories) { [story1, story2] } + + it "renders a Primer::Beta::BorderBox" do + render_component + + expect(page).to have_css(".Box") + end + + it "has the sprint ID in the DOM id" do + render_component + + expect(page).to have_css(".Box#backlog_#{sprint.id}") + end + + it "renders BacklogHeaderComponent in header" do + render_component + + expect(page).to have_css(".Box-header h4", text: "Sprint 1") + end + + it "renders StoryComponent for each story" do + render_component + + expect(page).to have_css(".Box-row", count: 3) # 2 stories + 1 empty list item + expect(page).to have_text(story1.subject) + expect(page).to have_text(story2.subject) + end + + it "has the empty blankslate row with data attribute" do + render_component + + # The empty row has data-empty-list-item attribute + expect(page).to have_css("[data-empty-list-item]", visible: :all) + end + + it "has drop target data attributes" do + render_component + + box = page.find(".Box") + expect(box["data-target-id"]).to eq(sprint.id.to_s) + expect(box["data-target-allowed-drag-type"]).to eq("story") + end + + it "has draggable data attributes on story rows" do + render_component + + story_row = page.find(".Box-row[id='story_#{story1.id}']") + expect(story_row["data-draggable-id"]).to eq(story1.id.to_s) + expect(story_row["data-draggable-type"]).to eq("story") + expect(story_row["data-drop-url"]).to include("move") + end + + it "renders story rows with proper classes" do + render_component + + story_row = page.find(".Box-row[id='story_#{story1.id}']") + expect(story_row[:class]).to include("Box-row--hover-gray") + expect(story_row[:class]).to include("Box-row--focus-blue") + expect(story_row[:class]).to include("Box-row--clickable") + end + end + + context "without stories" do + let(:stories) { [] } + let(:rendered_component) { render_component } + + it_behaves_like "rendering Blank Slate", heading: "Sprint Backlog is empty" + end + end +end diff --git a/modules/backlogs/spec/components/backlogs/backlog_header_component_spec.rb b/modules/backlogs/spec/components/backlogs/backlog_header_component_spec.rb new file mode 100644 index 00000000000..01023c78864 --- /dev/null +++ b/modules/backlogs/spec/components/backlogs/backlog_header_component_spec.rb @@ -0,0 +1,214 @@ +# 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 Backlogs::BacklogHeaderComponent, type: :component do + shared_let(:type_feature) { create(:type_feature) } + shared_let(:type_task) { create(:type_task) } + shared_let(:default_status) { create(:default_status) } + shared_let(:default_priority) { create(:default_priority) } + shared_let(:user) { create(:admin) } + current_user { user } + + let(:project) { create(:project, types: [type_feature, type_task]) } + let(:start_date) { Date.new(2024, 1, 15) } + let(:effective_date) { Date.new(2024, 1, 29) } + let(:sprint) { create(:sprint, project:, name: "Sprint 1", start_date:, effective_date:) } + let(:stories) { [] } + let(:backlog) { Backlog.new(sprint:, stories:) } + let(:state) { :show } + let(:folded) { false } + + before do + allow(Setting) + .to receive(:plugin_openproject_backlogs) + .and_return("story_types" => [type_feature.id.to_s], "task_type" => type_task.id.to_s) + end + + def render_component(state: :show, folded: false) + render_inline(described_class.new(backlog:, project:, state:, folded:, current_user: user)) + end + + describe "show state (default)" do + context "with stories" do + let(:story1) do + create(:story, + project:, + type: type_feature, + status: default_status, + priority: default_priority, + story_points: 5, + version: sprint) + end + let(:story2) do + create(:story, + project:, + type: type_feature, + status: default_status, + priority: default_priority, + story_points: 3, + version: sprint) + end + let(:story_with_nil_points) do + create(:story, + project:, + type: type_feature, + status: default_status, + priority: default_priority, + story_points: nil, + version: sprint) + end + let(:stories) { [story1, story2, story_with_nil_points] } + + it "displays sprint name in h4" do + render_component + + expect(page).to have_css("h4", text: "Sprint 1") + end + + it "shows story count via Primer::Beta::Counter" do + render_component + + expect(page).to have_css(".Counter", text: "3") + end + + it "shows formatted date range with time tags" do + render_component + + expect(page).to have_css("time[datetime='2024-01-15']") + expect(page).to have_css("time[datetime='2024-01-29']") + end + + it "shows story points total (nil treated as 0)" do + render_component + + # 5 + 3 + 0 = 8 points + expect(page).to have_text("8 points") + end + + it "renders collapse/expand chevrons" do + render_component + + expect(page).to have_octicon(:"chevron-up", visible: :all) + expect(page).to have_octicon(:"chevron-down", visible: :all) + end + + it "renders BacklogMenuComponent" do + render_component + + expect(page).to have_css("action-menu") + end + end + + context "with no stories" do + let(:stories) { [] } + + it "shows 0 story count" do + render_component + + expect(page).to have_css(".Counter", text: "0") + end + + it "shows 0 points" do + render_component + + expect(page).to have_text("0 points") + end + end + + context "when sprint has no dates" do + let(:sprint) { create(:sprint, project:, name: "Sprint 1", start_date: nil, effective_date: nil) } + + it "renders without date range" do + render_component + + expect(page).to have_no_css("time") + end + end + end + + describe "folded state" do + context "when folded is true" do + it "renders chevron-up hidden and chevron-down visible" do + render_component(folded: true) + + # When folded, chevron-up is hidden (has hidden attribute on svg) + # and chevron-down is visible (for expanding) + expect(page).to have_css("svg[hidden][data-target='collapsible-header.arrowUp']", visible: :hidden) + expect(page).to have_css("svg[data-target='collapsible-header.arrowDown']:not([hidden])", visible: :all) + end + end + + context "when folded is false" do + it "renders chevron-down hidden and chevron-up visible" do + render_component(folded: false) + + # When expanded, chevron-down is hidden (has hidden attribute) + # and chevron-up is visible (for collapsing) + expect(page).to have_css("svg[hidden][data-target='collapsible-header.arrowDown']", visible: :hidden) + expect(page).to have_css("svg[data-target='collapsible-header.arrowUp']:not([hidden])", visible: :all) + end + end + end + + describe "edit state" do + it "renders a form" do + render_component(state: :edit) + + expect(page).to have_css("form") + end + + it "renders text field for name" do + render_component(state: :edit) + + expect(page).to have_field(Sprint.human_attribute_name(:name), with: "Sprint 1") + end + + it "renders date picker components" do + render_component(state: :edit) + + # Date pickers have calendar icons as leading visuals + expect(page).to have_octicon(:calendar, count: 2) + end + + it "shows Save button" do + render_component(state: :edit) + + expect(page).to have_button(I18n.t(:button_save)) + end + + it "shows Cancel button" do + render_component(state: :edit) + + expect(page).to have_link(I18n.t(:button_cancel)) + end + end +end diff --git a/modules/backlogs/spec/components/backlogs/backlog_menu_component_spec.rb b/modules/backlogs/spec/components/backlogs/backlog_menu_component_spec.rb new file mode 100644 index 00000000000..30f26b2ccf7 --- /dev/null +++ b/modules/backlogs/spec/components/backlogs/backlog_menu_component_spec.rb @@ -0,0 +1,207 @@ +# 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 Backlogs::BacklogMenuComponent, type: :component do + shared_let(:type_feature) { create(:type_feature) } + shared_let(:type_task) { create(:type_task) } + + let(:project) { create(:project, types: [type_feature, type_task]) } + let(:sprint) { create(:sprint, project:, name: "Sprint 1", start_date: Date.yesterday, effective_date: Date.tomorrow) } + let(:stories) { [] } + let(:backlog) { Backlog.new(sprint:, stories:) } + let(:user) { create(:user) } + let(:permissions) { [] } + + before do + allow(Setting) + .to receive(:plugin_openproject_backlogs) + .and_return("story_types" => [type_feature.id.to_s], "task_type" => type_task.id.to_s) + + # Set up user with specific permissions + create(:member, + project:, + principal: user, + roles: [create(:project_role, permissions:)]) + login_as(user) + end + + def render_component + render_inline(described_class.new(backlog:, project:, current_user: user)) + end + + describe "permission-based items" do + context "with :update_sprints permission" do + let(:permissions) { %i[view_master_backlog update_sprints] } + + it "shows Edit item with pencil icon" do + render_component + + expect(page).to have_css("action-menu") + expect(page).to have_text(I18n.t("backlogs.backlog_menu_component.action_menu.edit_sprint")) + expect(page).to have_octicon(:pencil) + end + end + + context "without :update_sprints permission" do + let(:permissions) { [:view_master_backlog] } + + it "does not show Edit item" do + render_component + + expect(page).to have_no_text(I18n.t("backlogs.backlog_menu_component.action_menu.edit_sprint")) + end + end + + context "with :add_work_packages permission" do + let(:permissions) { %i[view_master_backlog add_work_packages] } + + it "shows Add new story item with compose icon" do + render_component + + expect(page).to have_text(I18n.t(:"backlogs.add_new_story")) + expect(page).to have_octicon(:compose) + end + end + + context "without :add_work_packages permission" do + let(:permissions) { [:view_master_backlog] } + + it "does not show Add new story item" do + render_component + + expect(page).to have_no_text(I18n.t(:"backlogs.add_new_story")) + end + end + + context "with :manage_versions permission" do + let(:permissions) { %i[view_master_backlog manage_versions] } + + it "shows Properties item with gear icon" do + render_component + + expect(page).to have_text(I18n.t(:"backlogs.properties")) + expect(page).to have_octicon(:gear) + end + end + + context "without :manage_versions permission" do + let(:permissions) { [:view_master_backlog] } + + it "does not show Properties item" do + render_component + + expect(page).to have_no_text(I18n.t(:"backlogs.properties")) + end + end + + context "with :view_taskboards permission" do + let(:permissions) { %i[view_master_backlog view_taskboards] } + + it "shows Task board item" do + render_component + + expect(page).to have_text(I18n.t(:label_task_board)) + end + end + + context "without :view_taskboards permission" do + let(:permissions) { [:view_master_backlog] } + + it "does not show Task board item" do + render_component + + expect(page).to have_no_text(I18n.t(:label_task_board)) + end + end + end + + describe "always-visible items" do + let(:permissions) { [:view_master_backlog] } + + it "shows Stories/Tasks link" do + render_component + + expect(page).to have_text(I18n.t(:label_stories_tasks)) + end + + it "shows Burndown chart link" do + render_component + + expect(page).to have_text(I18n.t(:"backlogs.show_burndown_chart")) + end + + context "when sprint has no burndown (no dates)" do + let(:sprint) { create(:sprint, project:, name: "Sprint 1", start_date: nil, effective_date: nil) } + + it "shows Burndown chart link as disabled" do + render_component + + burndown_item = page.find("li", text: I18n.t(:"backlogs.show_burndown_chart")) + expect(burndown_item[:class]).to include("ActionListItem--disabled") + end + end + + context "when sprint has burndown" do + it "shows Burndown chart link as enabled" do + render_component + + burndown_item = page.find("li", text: I18n.t(:"backlogs.show_burndown_chart")) + expect(burndown_item[:class]).not_to include("ActionListItem--disabled") + end + end + end + + describe "module-based items" do + context "when wiki module is enabled" do + let(:permissions) { [:view_master_backlog] } + let(:project) { create(:project, types: [type_feature, type_task], enabled_module_names: %w[backlogs wiki]) } + + it "shows Wiki item" do + render_component + + expect(page).to have_text(I18n.t(:label_wiki)) + expect(page).to have_octicon(:book) + end + end + + context "when wiki module is disabled" do + let(:permissions) { [:view_master_backlog] } + let(:project) { create(:project, types: [type_feature, type_task], enabled_module_names: %w[backlogs]) } + + it "does not show Wiki item" do + render_component + + expect(page).to have_no_text(I18n.t(:label_wiki)) + end + end + end +end diff --git a/modules/backlogs/spec/components/backlogs/sprint_page_header_component_spec.rb b/modules/backlogs/spec/components/backlogs/sprint_page_header_component_spec.rb new file mode 100644 index 00000000000..34c50fd51e8 --- /dev/null +++ b/modules/backlogs/spec/components/backlogs/sprint_page_header_component_spec.rb @@ -0,0 +1,123 @@ +# 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 Backlogs::SprintPageHeaderComponent, type: :component do + let(:project) { create(:project, name: "Test Project") } + let(:start_date) { Date.new(2024, 1, 15) } + let(:effective_date) { Date.new(2024, 1, 29) } + let(:sprint) { create(:sprint, project:, name: "Sprint 1", start_date:, effective_date:) } + + def render_component + render_inline(described_class.new(sprint:, project:)) + end + + describe "rendering" do + it "renders Primer::OpenProject::PageHeader" do + render_component + + expect(page).to have_css(".PageHeader") + end + + it "displays sprint name as title" do + render_component + + expect(page).to have_css(".PageHeader-title", text: "Sprint 1") + end + + it "shows date range in description with time tags" do + render_component + + expect(page).to have_css("time[datetime='2024-01-15']") + expect(page).to have_css("time[datetime='2024-01-29']") + end + + it "renders breadcrumbs" do + render_component + + expect(page).to have_css(".PageHeader-breadcrumbs") + end + + it "includes project link in breadcrumbs" do + render_component + + expect(page).to have_link("Test Project") + end + + it "includes backlogs link in breadcrumbs" do + render_component + + expect(page).to have_link(I18n.t(:label_backlogs)) + end + + it "includes sprint name as text (not link) in breadcrumbs" do + render_component + + # The last breadcrumb item should be the sprint name as plain text + breadcrumbs = page.find(".PageHeader-breadcrumbs") + expect(breadcrumbs).to have_text("Sprint 1") + end + end + + describe "date handling" do + context "when sprint has only start_date" do + let(:sprint) { create(:sprint, project:, name: "Sprint 1", start_date:, effective_date: nil) } + + it "renders only start date" do + render_component + + expect(page).to have_css("time[datetime='2024-01-15']") + expect(page).to have_no_css("time[datetime='2024-01-29']") + end + end + + context "when sprint has only effective_date" do + let(:sprint) { create(:sprint, project:, name: "Sprint 1", start_date: nil, effective_date:) } + + it "renders only effective date" do + render_component + + expect(page).to have_no_css("time[datetime='2024-01-15']") + expect(page).to have_css("time[datetime='2024-01-29']") + end + end + + context "when sprint has no dates" do + let(:sprint) { create(:sprint, project:, name: "Sprint 1", start_date: nil, effective_date: nil) } + + it "renders no time elements" do + render_component + + expect(page).to have_no_css("time") + end + end + end +end diff --git a/modules/backlogs/spec/components/backlogs/story_component_spec.rb b/modules/backlogs/spec/components/backlogs/story_component_spec.rb new file mode 100644 index 00000000000..e09562c2701 --- /dev/null +++ b/modules/backlogs/spec/components/backlogs/story_component_spec.rb @@ -0,0 +1,133 @@ +# 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 Backlogs::StoryComponent, type: :component do + shared_let(:type_feature) { create(:type_feature) } + shared_let(:type_task) { create(:type_task) } + shared_let(:default_status) { create(:default_status) } + shared_let(:default_priority) { create(:default_priority) } + shared_let(:user) { create(:admin) } + current_user { user } + + let(:project) { create(:project, types: [type_feature, type_task]) } + let(:sprint) { create(:sprint, project:, name: "Sprint 1", start_date: Date.yesterday, effective_date: Date.tomorrow) } + let(:story_points) { 5 } + let(:story) do + create(:story, + subject: "Test Story Subject", + project:, + type: type_feature, + status: default_status, + priority: default_priority, + story_points:, + position: 1, + version: sprint) + end + let(:max_position) { 3 } + + before do + allow(Setting) + .to receive(:plugin_openproject_backlogs) + .and_return("story_types" => [type_feature.id.to_s], "task_type" => type_task.id.to_s) + end + + def render_component + render_inline(described_class.new(story:, sprint:, max_position:, current_user: user)) + end + + describe "rendering" do + it "renders Primer::OpenProject::DragHandle" do + render_component + + # DragHandle renders with grabber icon + expect(page).to have_octicon(:grabber) + end + + it "renders WorkPackages::InfoLineComponent" do + render_component + + # InfoLine renders type and ID info + expect(page).to have_text("FEATURE") + expect(page).to have_text("##{story.id}") + end + + it "shows story subject in semibold text" do + render_component + + expect(page).to have_text("Test Story Subject") + end + + it "shows story points" do + render_component + + expect(page).to have_text("5 points") + end + + it "renders StoryMenuComponent" do + render_component + + expect(page).to have_css("action-menu") + end + end + + describe "story points handling" do + context "when story_points is nil" do + let(:story_points) { nil } + + it "shows 0 points" do + render_component + + expect(page).to have_text("0 points") + end + end + + context "when story_points is 0" do + let(:story_points) { 0 } + + it "shows 0 points" do + render_component + + expect(page).to have_text("0 points") + end + end + + context "when story_points is 1" do + let(:story_points) { 1 } + + it "shows 1 point (singular)" do + render_component + + expect(page).to have_text("1 point") + end + end + end +end diff --git a/modules/backlogs/spec/components/backlogs/story_menu_component_spec.rb b/modules/backlogs/spec/components/backlogs/story_menu_component_spec.rb new file mode 100644 index 00000000000..7a77d94a9a9 --- /dev/null +++ b/modules/backlogs/spec/components/backlogs/story_menu_component_spec.rb @@ -0,0 +1,195 @@ +# 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 Backlogs::StoryMenuComponent, type: :component do + shared_let(:type_feature) { create(:type_feature) } + shared_let(:type_task) { create(:type_task) } + shared_let(:default_status) { create(:default_status) } + shared_let(:default_priority) { create(:default_priority) } + shared_let(:user) { create(:admin) } + current_user { user } + + let(:project) { create(:project, types: [type_feature, type_task]) } + let(:sprint) { create(:sprint, project:, name: "Sprint 1", start_date: Date.yesterday, effective_date: Date.tomorrow) } + let(:position) { 2 } + let(:max_position) { 3 } + let(:story) do + create(:story, + subject: "Test Story", + project:, + type: type_feature, + status: default_status, + priority: default_priority, + story_points: 5, + position:, + version: sprint) + end + + before do + allow(Setting) + .to receive(:plugin_openproject_backlogs) + .and_return("story_types" => [type_feature.id.to_s], "task_type" => type_task.id.to_s) + end + + def render_component(position: 2, max_position: 3) + story.update!(position:) + render_inline(described_class.new(story:, sprint:, max_position:, current_user: user)) + end + + describe "standard items" do + it "shows Open details link (split view)" do + render_component + + expect(page).to have_text(I18n.t(:"js.button_open_details")) + expect(page).to have_octicon(:"op-view-split") + end + + it "shows Open fullscreen link (full page)" do + render_component + + expect(page).to have_text(I18n.t(:"js.button_open_fullscreen")) + expect(page).to have_octicon(:"screen-full") + end + + it "shows a divider before move options" do + render_component + + expect(page).to have_css(".ActionList-sectionDivider") + end + end + + describe "move menu items" do + it "shows Move to top item with move-to-top icon" do + render_component + + expect(page).to have_text(I18n.t(:label_sort_highest)) + expect(page).to have_octicon(:"move-to-top") + end + + it "shows Move up item with chevron-up icon" do + render_component + + expect(page).to have_text(I18n.t(:label_sort_higher)) + expect(page).to have_octicon(:"chevron-up") + end + + it "shows Move down item with chevron-down icon" do + render_component + + expect(page).to have_text(I18n.t(:label_sort_lower)) + expect(page).to have_octicon(:"chevron-down") + end + + it "shows Move to bottom item with move-to-bottom icon" do + render_component + + expect(page).to have_text(I18n.t(:label_sort_lowest)) + expect(page).to have_octicon(:"move-to-bottom") + end + end + + describe "position logic" do + context "when item is first (position=1)" do + it "disables Move to top and Move up" do + render_component(position: 1, max_position: 3) + + # Move to top should be disabled + move_to_top = page.find("li", text: I18n.t(:label_sort_highest)) + expect(move_to_top[:class]).to include("ActionListItem--disabled") + + # Move up should be disabled + move_up = page.find("li", text: I18n.t(:label_sort_higher)) + expect(move_up[:class]).to include("ActionListItem--disabled") + end + + it "enables Move down and Move to bottom" do + render_component(position: 1, max_position: 3) + + # Move down should be enabled + move_down = page.find("li", text: I18n.t(:label_sort_lower)) + expect(move_down[:class]).not_to include("ActionListItem--disabled") + + # Move to bottom should be enabled + move_to_bottom = page.find("li", text: I18n.t(:label_sort_lowest)) + expect(move_to_bottom[:class]).not_to include("ActionListItem--disabled") + end + end + + context "when item is last (position=max)" do + it "disables Move down and Move to bottom" do + render_component(position: 3, max_position: 3) + + # Move down should be disabled + move_down = page.find("li", text: I18n.t(:label_sort_lower)) + expect(move_down[:class]).to include("ActionListItem--disabled") + + # Move to bottom should be disabled + move_to_bottom = page.find("li", text: I18n.t(:label_sort_lowest)) + expect(move_to_bottom[:class]).to include("ActionListItem--disabled") + end + + it "enables Move to top and Move up" do + render_component(position: 3, max_position: 3) + + # Move to top should be enabled + move_to_top = page.find("li", text: I18n.t(:label_sort_highest)) + expect(move_to_top[:class]).not_to include("ActionListItem--disabled") + + # Move up should be enabled + move_up = page.find("li", text: I18n.t(:label_sort_higher)) + expect(move_up[:class]).not_to include("ActionListItem--disabled") + end + end + + context "when item is in the middle" do + it "enables all move options" do + render_component(position: 2, max_position: 3) + + expect(page.find("li", text: I18n.t(:label_sort_highest))[:class]).not_to include("ActionListItem--disabled") + expect(page.find("li", text: I18n.t(:label_sort_higher))[:class]).not_to include("ActionListItem--disabled") + expect(page.find("li", text: I18n.t(:label_sort_lower))[:class]).not_to include("ActionListItem--disabled") + expect(page.find("li", text: I18n.t(:label_sort_lowest))[:class]).not_to include("ActionListItem--disabled") + end + end + + context "when there is only one item (position=1, max=1)" do + it "disables all move options" do + render_component(position: 1, max_position: 1) + + expect(page.find("li", text: I18n.t(:label_sort_highest))[:class]).to include("ActionListItem--disabled") + expect(page.find("li", text: I18n.t(:label_sort_higher))[:class]).to include("ActionListItem--disabled") + expect(page.find("li", text: I18n.t(:label_sort_lower))[:class]).to include("ActionListItem--disabled") + expect(page.find("li", text: I18n.t(:label_sort_lowest))[:class]).to include("ActionListItem--disabled") + end + end + end +end diff --git a/modules/backlogs/spec/controllers/rb_master_backlogs_controller_spec.rb b/modules/backlogs/spec/controllers/rb_master_backlogs_controller_spec.rb new file mode 100644 index 00000000000..595d6f32d70 --- /dev/null +++ b/modules/backlogs/spec/controllers/rb_master_backlogs_controller_spec.rb @@ -0,0 +1,70 @@ +# 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 RbMasterBacklogsController do + shared_let(:type_feature) { create(:type_feature) } + shared_let(:type_task) { create(:type_task) } + shared_let(:user) { create(:admin) } + current_user { user } + + let(:project) { create(:project) } + let(:status) { create(:status, name: "status 1", is_default: true) } + let(:sprint) { create(:sprint, project:) } + let(:story) { create(:story, status:, version: sprint, project:) } + + before do + allow(Setting) + .to receive(:plugin_openproject_backlogs) + .and_return({ "story_types" => [type_feature.id], "task_type" => type_task.id }) + end + + describe "GET #index" do + it do + get :index, params: { project_id: project.id } + + expect(response).to be_successful + end + end + + describe "GET #split_view" do + it do + get :split_view, params: { + project_id: project.id, + tab: :overview, + work_package_id: story.id, + work_package_split_view: true + } + + expect(response).to be_successful + end + end +end diff --git a/modules/backlogs/spec/controllers/rb_sprints_controller_spec.rb b/modules/backlogs/spec/controllers/rb_sprints_controller_spec.rb new file mode 100644 index 00000000000..cb1a310ac48 --- /dev/null +++ b/modules/backlogs/spec/controllers/rb_sprints_controller_spec.rb @@ -0,0 +1,126 @@ +# 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 RbSprintsController do + shared_let(:type_feature) { create(:type_feature) } + shared_let(:type_task) { create(:type_task) } + shared_let(:user) { create(:admin) } + current_user { user } + + before do + allow(Setting) + .to receive(:plugin_openproject_backlogs) + .and_return({ "story_types" => [type_feature.id], "task_type" => type_task.id }) + + allow(Project) + .to receive(:find) + .with(project.identifier) + .and_return(project) + + allow(Sprint) + .to receive(:find) + .with(sprint.id.to_s) + .and_return(sprint) + end + + describe "GET #edit_name" do + let(:project) { build_stubbed(:project) } + let(:sprint) { build_stubbed(:sprint) } + + it "responds with success", :aggregate_failures do + get :edit_name, params: { project_id: project.identifier, id: sprint.id }, format: :turbo_stream + + expect(response).to be_successful + expect(response).to have_http_status :ok + expect(response).to have_turbo_stream action: "update", target: "backlogs-backlog-header-component-#{sprint.id}" + end + end + + describe "GET #show_name" do + let(:project) { build_stubbed(:project) } + let(:sprint) { build_stubbed(:sprint) } + + it "responds with success", :aggregate_failures do + get :show_name, params: { project_id: project.identifier, id: sprint.id }, format: :turbo_stream + + expect(response).to be_successful + expect(response).to have_http_status :ok + expect(response).to have_turbo_stream action: "update", target: "backlogs-backlog-header-component-#{sprint.id}" + end + end + + describe "PATCH #update" do + let(:project) { build_stubbed(:project) } + let(:sprint) { build_stubbed(:sprint) } + + before do + update_service = instance_double(Versions::UpdateService, call: service_result) + + allow(Versions::UpdateService) + .to receive(:new) + .with(user:, model: sprint) + .and_return(update_service) + end + + context "when service call succeeds" do + let(:service_result) { ServiceResult.success(result: sprint) } + + it "responds with success", :aggregate_failures do + patch :update, params: { project_id: project.identifier, id: sprint.id, sprint: { name: "Updated Sprint" } }, + format: :turbo_stream + + expect(response).to be_successful + expect(response).to have_http_status :ok + expect(response).to have_turbo_stream action: "update", target: "backlogs-backlog-header-component-#{sprint.id}" + expect(response).to have_turbo_stream action: "flash", target: "op-primer-flash-component" + end + end + + context "when service call fails" do + let(:service_result) { ServiceResult.failure(result: sprint) } + + before do + project.name = "" + end + + it "responds with 422", :aggregate_failures do + patch :update, params: { project_id: project.identifier, id: sprint.id, sprint: { name: "" } }, + format: :turbo_stream + + expect(response).not_to be_successful + expect(response).to have_http_status :unprocessable_entity + expect(response).to have_turbo_stream action: "update", target: "backlogs-backlog-header-component-#{sprint.id}" + expect(response).to have_turbo_stream action: "flash", target: "op-primer-flash-component" + end + end + end +end diff --git a/modules/backlogs/spec/controllers/rb_stories_controller_spec.rb b/modules/backlogs/spec/controllers/rb_stories_controller_spec.rb new file mode 100644 index 00000000000..096c4a0d608 --- /dev/null +++ b/modules/backlogs/spec/controllers/rb_stories_controller_spec.rb @@ -0,0 +1,102 @@ +# 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 RbStoriesController do + shared_let(:type_feature) { create(:type_feature) } + shared_let(:type_task) { create(:type_task) } + shared_let(:user) { create(:admin) } + current_user { user } + + let(:project) { create(:project) } + let(:status) { create(:status, name: "status 1", is_default: true) } + let(:sprint) { create(:sprint, project:) } + let(:story) { create(:story, status:, version: sprint, project:) } + + before do + allow(Setting) + .to receive(:plugin_openproject_backlogs) + .and_return({ "story_types" => [type_feature.id], "task_type" => type_task.id }) + end + + describe "POST #create" do + it "responds with success", :aggregate_failures do + post :create, params: { project_id: project.id, sprint_id: sprint.id }, + format: :turbo_stream + + expect(response).to be_successful + expect(response).to have_http_status :ok + end + end + + describe "PUT #move" do + let(:other_sprint) { create(:sprint, name: "Sprint 2", project:) } + + it "responds with success", :aggregate_failures do + put :move, params: { + project_id: project.id, + sprint_id: sprint.id, + id: story.id, + target_id: other_sprint.id, + position: 1 + }, + format: :turbo_stream + + expect(response).to be_successful + expect(response).to have_http_status :ok + expect(response).to have_turbo_stream action: "replace", target: "backlogs-backlog-component-#{sprint.id}" + expect(response).to have_turbo_stream action: "replace", target: "backlogs-backlog-component-#{other_sprint.id}" + expect(response).to have_turbo_stream action: "flash", target: "op-primer-flash-component" + end + end + + describe "POST #reorder" do + it "responds with success", :aggregate_failures do + post :reorder, params: { project_id: project.id, sprint_id: sprint.id, id: story.id, direction: "highest" }, + format: :turbo_stream + + expect(response).to be_successful + expect(response).to have_http_status :ok + expect(response).to have_turbo_stream action: "replace", target: "backlogs-backlog-component-#{sprint.id}" + expect(response).to have_turbo_stream action: "flash", target: "op-primer-flash-component" + end + end + + describe "PATCH #update" do + it "responds with success", :aggregate_failures do + patch :update, params: { project_id: project.id, sprint_id: sprint.id, id: story.id }, + format: :turbo_stream + + expect(response).to be_successful + expect(response).to have_http_status :ok + end + end +end diff --git a/modules/backlogs/spec/features/backlogs/change_status_spec.rb b/modules/backlogs/spec/features/backlogs/change_status_spec.rb index 028e3d04fe1..9077e4230ad 100644 --- a/modules/backlogs/spec/features/backlogs/change_status_spec.rb +++ b/modules/backlogs/spec/features/backlogs/change_status_spec.rb @@ -31,7 +31,7 @@ require "spec_helper" require_relative "../../support/pages/backlogs" -RSpec.describe "Backlogs context menu", :js do +RSpec.describe.skip "Backlogs change status", :js do shared_let(:story_type) { create(:type_feature) } shared_let(:task_type) { create(:type_task) } shared_let(:project) { create(:project, types: [story_type, task_type]) } diff --git a/modules/backlogs/spec/features/backlogs/context_menu_spec.rb b/modules/backlogs/spec/features/backlogs/context_menu_spec.rb index e64d2d59ed8..85ac48a307c 100644 --- a/modules/backlogs/spec/features/backlogs/context_menu_spec.rb +++ b/modules/backlogs/spec/features/backlogs/context_menu_spec.rb @@ -113,9 +113,9 @@ RSpec.describe "Backlogs context menu", :js do sprint.update(start_date: nil) end - it 'does not display the "Burndown chart" menu entry' do + it 'disables the "Burndown chart" menu entry' do within_backlog_context_menu do |menu| - expect(menu).to have_no_link I18n.t("backlogs.show_burndown_chart") + expect(menu).to have_link I18n.t("backlogs.show_burndown_chart", aria: { disabled: true }) end end end @@ -125,9 +125,9 @@ RSpec.describe "Backlogs context menu", :js do sprint.update(effective_date: nil) end - it 'does not display the "Burndown chart" menu entry' do + it 'disables the "Burndown chart" menu entry' do within_backlog_context_menu do |menu| - expect(menu).to have_no_link I18n.t("backlogs.show_burndown_chart") + expect(menu).to have_link I18n.t("backlogs.show_burndown_chart", aria: { disabled: true }) end end end diff --git a/modules/backlogs/spec/features/backlogs/create_story_spec.rb b/modules/backlogs/spec/features/backlogs/create_story_spec.rb index abb3f9620c8..c2d8928e4f2 100644 --- a/modules/backlogs/spec/features/backlogs/create_story_spec.rb +++ b/modules/backlogs/spec/features/backlogs/create_story_spec.rb @@ -27,6 +27,7 @@ #++ require "spec_helper" +require_relative "../../support/pages/backlogs" RSpec.describe "Backlogs", :js, :selenium, driver: :firefox_de do # using FF due to regression #64158 let(:story_type) do @@ -88,6 +89,8 @@ RSpec.describe "Backlogs", :js, :selenium, driver: :firefox_de do # using FF due create(:default_priority) end + let(:backlogs_page) { Pages::Backlogs.new(project) } + before do login_as(user) @@ -100,32 +103,36 @@ RSpec.describe "Backlogs", :js, :selenium, driver: :firefox_de do # using FF due end it "allows creating a new story" do - visit backlogs_project_backlogs_path(project) + backlogs_page.visit! - within("#backlog_#{backlog_version.id}", wait: 10) do - menu = find(".backlog-menu") - menu.click - click_link "New Story" + backlogs_page.click_in_backlog_menu(backlog_version, "New story") + + within_dialog "New work package" do fill_in "Subject", with: "The new story" - fill_in "Story Points", with: "5" + # TODO: removed in OP #57688, to be reimplemented + # fill_in "Story Points", with: "5" - # inactive types should not be selectable - # but the user can choose from the active types - expect(page) - .to have_no_css("option", text: inactive_story_type.name) + # inactive types should not be selectable but the user can choose from the + # active types + # TODO: removed in OP #57688, to be reimplemented + # expect(page).to have_no_css("option", text: inactive_story_type.name) - select story_type2.name, from: "Type" + select_combo_box_option story_type2.name, from: "Type" # saving the new story - find(:css, "input[name=subject]").native.send_key :return + click_on "Create" + end - # velocity should be summed up immediately - expect(page) - .to have_css(".velocity", text: "12") + expect_and_dismiss_flash type: :success, message: "New work package created" - # this will ensure that the page refresh is through before we check the order - menu.click - click_link "New Story" + # velocity should be summed up immediately + # TODO: removed in OP #57688, to be reimplemented + # xpect(page).to have_css(".velocity", text: "12") + + # this will ensure that the page refresh is through before we check the order + backlogs_page.click_in_backlog_menu(backlog_version, "New story") + + within_dialog "New work package" do fill_in "Subject", with: "Another story" end @@ -135,15 +142,15 @@ RSpec.describe "Backlogs", :js, :selenium, driver: :firefox_de do # using FF due expect(page) .to have_no_content "Another story" - expect(page) - .to have_css ".story:nth-of-type(1)", text: "The new story" - expect(page) - .to have_css ".story:nth-of-type(2)", text: existing_story1.subject - expect(page) - .to have_css ".story:nth-of-type(3)", text: existing_story2.subject + new_story = WorkPackage.find_by(subject: "The new story") - # created with the selected type - expect(page) - .to have_css ".story:nth-of-type(1) .type_id", text: story_type2.name + # stories are ordered by position (ASC), with NULL positions at the end ordered by ID + # existing stories have positions 1 and 2, new story has no position so appears at end + backlogs_page.expect_stories_in_order(backlog_version, existing_story1, existing_story2, new_story) + + # created with the selected type (HighlightedTypeComponent renders type name in uppercase) + within("#story_#{new_story.id}") do + expect(page).to have_text(story_type2.name.upcase) + end end end diff --git a/modules/backlogs/spec/features/empty_backlogs_spec.rb b/modules/backlogs/spec/features/empty_backlogs_spec.rb index b4cf24b380f..b558b1914ac 100644 --- a/modules/backlogs/spec/features/empty_backlogs_spec.rb +++ b/modules/backlogs/spec/features/empty_backlogs_spec.rb @@ -48,12 +48,11 @@ RSpec.describe "Empty backlogs project", context "as admin" do let(:current_user) { create(:admin) } - it "shows a no results box with action" do - expect(page).to have_css(".generic-table--no-results-container", text: I18n.t(:backlogs_empty_title)) - expect(page).to have_css(".generic-table--no-results-description", text: I18n.t(:backlogs_empty_action_text)) - - link = page.find ".generic-table--no-results-description a" - expect(link[:href]).to include(new_project_version_path(project)) + it "shows blankslate with description" do + within ".blankslate" do + expect(page).to have_heading(I18n.t(:backlogs_empty_title)) + expect(page).to have_text(I18n.t(:backlogs_empty_action_text)) + end end end @@ -61,9 +60,11 @@ RSpec.describe "Empty backlogs project", let(:role) { create(:project_role, permissions: %i(view_master_backlog)) } let(:current_user) { create(:user, member_with_roles: { project => role }) } - it "only shows a no results box" do - expect(page).to have_css(".generic-table--no-results-container", text: I18n.t(:backlogs_empty_title)) - expect(page).to have_no_css(".generic-table--no-results-description") + it "shows a blankslate without description" do + within ".blankslate" do + expect(page).to have_heading(I18n.t(:backlogs_empty_title)) + expect(page).to have_no_text(I18n.t(:backlogs_empty_action_text)) + end end end end diff --git a/modules/backlogs/spec/features/stories_in_backlog_spec.rb b/modules/backlogs/spec/features/stories_in_backlog_spec.rb index 26ee04562ec..5cc482d6688 100644 --- a/modules/backlogs/spec/features/stories_in_backlog_spec.rb +++ b/modules/backlogs/spec/features/stories_in_backlog_spec.rb @@ -168,162 +168,5 @@ RSpec.describe "Stories in backlog", :js, # Velocity is calculated by summing up all story points in a sprint backlogs_page .expect_velocity(sprint, 30) - - SeleniumHubWaiter.wait - # Creating a story - backlogs_page - .click_in_backlog_menu(sprint, "New Story") - - SeleniumHubWaiter.wait - backlogs_page - .edit_new_story(subject: "New story", - story_points: 10) - - new_story = nil - retry_block do - new_story = WorkPackage.find_by(subject: "New story") - raise "Expected story" unless new_story - end - - backlogs_page - .expect_story_in_sprint(new_story, sprint) - - # All positions will be unique in the sprint - expect(Story.where(version: sprint, type: story, project:).pluck(:position)) - .to contain_exactly(1, 2, 3) - - backlogs_page - .expect_stories_in_order(sprint, new_story, sprint_story1, sprint_story2) - - # Creating the story will update the velocity - backlogs_page - .expect_velocity(sprint, 40) - - # Editing in a sprint - - SeleniumHubWaiter.wait - backlogs_page - .edit_story(sprint_story1, - subject: "Altered story1", - story_points: 15) - - retry_block do - sprint_story1.reload - raise "Expected story to be renamed" unless sprint_story1.subject == "Altered story1" - end - - backlogs_page - .expect_for_story(sprint_story1, subject: "Altered story1") - - # Updating the story_points of a story will update the velocity of the sprint - backlogs_page - .expect_velocity(sprint, 45) - - SeleniumHubWaiter.wait - # Moving a story to top - backlogs_page - .drag_in_sprint(sprint_story1, new_story) - - backlogs_page - .expect_stories_in_order(sprint, sprint_story1, new_story, sprint_story2) - - expect(Story.where(version: sprint, type: story, project:).pluck(:position)) - .to contain_exactly(1, 2, 3) - - # Moving a story to bottom - backlogs_page - .drag_in_sprint(sprint_story1, sprint_story2, before: false) - - backlogs_page - .expect_stories_in_order(sprint, new_story, sprint_story2, sprint_story1) - - expect(Story.where(version: sprint, type: story, project:).pluck(:position)) - .to contain_exactly(1, 2, 3) - - # Moving a story to from the backlog to the sprint (3rd position) - - SeleniumHubWaiter.wait - backlogs_page - .drag_in_sprint(backlog_story1, sprint_story2, before: false) - - backlogs_page - .expect_stories_in_order(sprint, new_story, sprint_story2, backlog_story1, sprint_story1) - - expect(Story.where(version: sprint, type: story, project:).pluck(:position)) - .to contain_exactly(1, 2, 3, 4) - - # Available statuses when editing - - backlogs_page - .enter_edit_story_mode(backlog_story1) - - # The available statuses include those available by the workflow: - # Current and every reachable one - backlogs_page - .expect_status_options(backlog_story1, - [default_status, other_status]) - - SeleniumHubWaiter.wait - backlogs_page - .alter_attributes_in_edit_story_mode(backlog_story1, - subject: "Altered backlog story1", - status: other_status.name) - backlogs_page - .save_story_from_edit_mode(backlog_story1) - - retry_block do - backlog_story1.reload - raise "Expected story to be renamed" unless backlog_story1.subject == "Altered backlog story1" - end - - expect(backlog_story1.status) - .to eql other_status - - backlogs_page - .expect_for_story(backlog_story1, - subject: "Altered backlog story1", - status: other_status.name) - - SeleniumHubWaiter.wait - backlogs_page - .enter_edit_story_mode(backlog_story1) - - # Since we switched to other status, only the current status and the next one is available now. - backlogs_page - .expect_status_options(backlog_story1, - [other_status]) - - # Available statuses when editing and switching the type - backlogs_page - .alter_attributes_in_edit_story_mode(backlog_story1, - type: other_story) - # This will result in an error as the current status is not available - backlogs_page - .save_story_from_edit_mode(backlog_story1) - - backlogs_page - .expect_for_story(backlog_story1, - subject: "Altered backlog story1", - status: default_status.name, - type: other_story.name) - - # Clicking would lead to having the burndown chart opened in another tab - # which seems hard to test with selenium. - backlogs_page - .expect_in_backlog_menu(sprint, "Burndown Chart") - - # One can switch to the work package page by clicking on the id - # Clicking on it will open the wp in another tab which seems to trip up selenium. - backlogs_page - .expect_story_link_to_wp_page(sprint_story1) - - # Go to the index page of work packages within that sprint via the menu - backlogs_page - .click_in_backlog_menu(sprint, "Stories/Tasks") - - wp_table = Pages::WorkPackagesTable.new(project) - - wp_table - .expect_work_package_listed(new_story, sprint_story2, backlog_story1, sprint_story1) end end diff --git a/modules/backlogs/spec/models/backlog_spec.rb b/modules/backlogs/spec/models/backlog_spec.rb index c7fa2300839..8e3c4d2f75c 100644 --- a/modules/backlogs/spec/models/backlog_spec.rb +++ b/modules/backlogs/spec/models/backlog_spec.rb @@ -53,4 +53,19 @@ RSpec.describe Backlog do end end end + + describe "ActiveModel naming" do + let(:sprint) { build_stubbed(:sprint) } + + subject(:instance) { described_class.new(sprint:, stories: []) } + + it "exposes an ActiveModel model_name" do + expect(described_class).to respond_to(:model_name) + expect(described_class.model_name).to respond_to(:param_key) + end + + it "implements #to_key" do + expect(instance).to respond_to(:to_key) + end + end end diff --git a/modules/backlogs/spec/routing/rb_master_backlogs_routing_spec.rb b/modules/backlogs/spec/routing/rb_master_backlogs_routing_spec.rb index bf96ed71ff0..b8ca73aab0a 100644 --- a/modules/backlogs/spec/routing/rb_master_backlogs_routing_spec.rb +++ b/modules/backlogs/spec/routing/rb_master_backlogs_routing_spec.rb @@ -35,5 +35,16 @@ RSpec.describe RbMasterBacklogsController do action: "index", project_id: "project_42") } + + it { + expect(get("/projects/project_42/backlogs/details/33")).to route_to( + controller: "rb_master_backlogs", + action: "split_view", + project_id: "project_42", + work_package_id: "33", + tab: :overview, + work_package_split_view: true + ) + } end end diff --git a/modules/backlogs/spec/routing/rb_sprints_routing_spec.rb b/modules/backlogs/spec/routing/rb_sprints_routing_spec.rb index 03f8f518e3d..1390a902423 100644 --- a/modules/backlogs/spec/routing/rb_sprints_routing_spec.rb +++ b/modules/backlogs/spec/routing/rb_sprints_routing_spec.rb @@ -30,6 +30,24 @@ require "spec_helper" RSpec.describe RbSprintsController do describe "routing" do + it { + expect(get("/projects/project_42/sprints/21/edit_name")).to route_to( + controller: "rb_sprints", + action: "edit_name", + project_id: "project_42", + id: "21" + ) + } + + it { + expect(get("/projects/project_42/sprints/21/show_name")).to route_to( + controller: "rb_sprints", + action: "show_name", + project_id: "project_42", + id: "21" + ) + } + it { expect(put("/projects/project_42/sprints/21")).to route_to(controller: "rb_sprints", action: "update", diff --git a/modules/backlogs/spec/routing/rb_stories_routing_spec.rb b/modules/backlogs/spec/routing/rb_stories_routing_spec.rb index 14d137d7d6c..c6dcb09166b 100644 --- a/modules/backlogs/spec/routing/rb_stories_routing_spec.rb +++ b/modules/backlogs/spec/routing/rb_stories_routing_spec.rb @@ -37,6 +37,26 @@ RSpec.describe RbStoriesController do sprint_id: "21") } + it { + expect(put("/projects/project_42/sprints/21/stories/85/move")).to route_to( + controller: "rb_stories", + action: "move", + project_id: "project_42", + sprint_id: "21", + id: "85" + ) + } + + it { + expect(post("/projects/project_42/sprints/21/stories/85/reorder")).to route_to( + controller: "rb_stories", + action: "reorder", + project_id: "project_42", + sprint_id: "21", + id: "85" + ) + } + it { expect(put("/projects/project_42/sprints/21/stories/85")).to route_to(controller: "rb_stories", action: "update", diff --git a/modules/backlogs/spec/support/pages/backlogs.rb b/modules/backlogs/spec/support/pages/backlogs.rb index 7e8fe177624..c41a5cbc19e 100644 --- a/modules/backlogs/spec/support/pages/backlogs.rb +++ b/modules/backlogs/spec/support/pages/backlogs.rb @@ -45,8 +45,8 @@ module Pages end def enter_edit_backlog_mode(backlog) - within_backlog(backlog) do - find(".start_date.editable").click + within_backlog_menu(backlog) do |menu| + menu.find(:menuitem, "Edit sprint").click end end @@ -77,11 +77,11 @@ module Pages attributes.each do |key, value| case key when :name - find("input[name=name]").set value + fill_in "Name", with: value when :start_date - find("input[name=start_date]").set value + fill_in "Start date", with: value when :effective_date - find("input[name=effective_date]").set value + fill_in "Finish date", with: value else raise NotImplementedError end @@ -109,10 +109,7 @@ module Pages def save_backlog_from_edit_mode(backlog) within_backlog(backlog) do - find("input[name=name]").native.send_key :return - - expect(page) - .to have_css(".start_date.editable") + find_field("Name").send_keys :return end end @@ -136,17 +133,9 @@ module Pages save_story_from_edit_mode(story) end - def edit_new_story(attributes) - within(".story.editing") do - alter_attributes_in_edit_story_mode(nil, attributes) - - save_story_from_edit_mode(nil) - end - end - def click_in_backlog_menu(backlog, item_name) within_backlog_menu(backlog) do |menu| - menu.find(".item", text: item_name).click + menu.find(:menuitem, text: item_name).click end end @@ -160,7 +149,7 @@ module Pages def fold_backlog(backlog) within_backlog(backlog) do - find(".toggler").click + find(:button, accessible_name: "Collapse/Expand #{backlog.name}").click end end @@ -252,12 +241,9 @@ module Pages end def expect_and_dismiss_error(message) - within ".ui-dialog" do - expect(page) - .to have_content message + expect(page).to have_content message - click_button("OK") - end + click_on "Cancel" end def path @@ -266,10 +252,9 @@ module Pages def within_backlog_menu(backlog, &) within_backlog(backlog) do - menu = find(".backlog-menu") - menu.click + find(:button, accessible_name: "Backlog actions").click - yield menu + within(:menu, &) end end diff --git a/modules/backlogs/spec/views/rb_burndown_charts/show_spec.rb b/modules/backlogs/spec/views/rb_burndown_charts/show_spec.rb index d7e09fa8f8c..a950505fdf6 100644 --- a/modules/backlogs/spec/views/rb_burndown_charts/show_spec.rb +++ b/modules/backlogs/spec/views/rb_burndown_charts/show_spec.rb @@ -117,7 +117,7 @@ RSpec.describe "rb_burndown_charts/show" do render expect(view).to render_template(partial: "_burndown", count: 0) - expect(rendered).to include(I18n.t("backlogs.no_burndown_data")) + expect(rendered).to include(I18n.t("rb_burndown_charts.show.blankslate_title")) end end end From 73d891d5d379f259c35af83404dfb0848f17aedd Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Sat, 24 Jan 2026 21:46:38 -0300 Subject: [PATCH 071/293] Burndown chart: Force Turbo to perform page reload --- modules/backlogs/app/views/rb_burndown_charts/show.html.erb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/backlogs/app/views/rb_burndown_charts/show.html.erb b/modules/backlogs/app/views/rb_burndown_charts/show.html.erb index dfc5f70dd02..86ea72309d8 100644 --- a/modules/backlogs/app/views/rb_burndown_charts/show.html.erb +++ b/modules/backlogs/app/views/rb_burndown_charts/show.html.erb @@ -29,6 +29,10 @@ See COPYRIGHT and LICENSE files for more details. <% html_title @sprint.name %> +<%= content_for :header_tags do %> + +<% end -%> + <%= render(Backlogs::SprintPageHeaderComponent.new(sprint: @sprint, project: @project)) %> <% if @burndown %> From 3838dd6091c6496c6f51393e813b57313a7c6899 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Tue, 27 Jan 2026 12:49:30 -0300 Subject: [PATCH 072/293] UX/UI improvements: responsiveness, story layout Multiple design fixes based on UX/UI feedback: - improvements to Backlogs header responsiveness (including form). - harmonization of story row layout with other modules using Border Box lists (i.e. Meetings). --- app/components/primer/component_helpers.rb | 41 --------- frontend/src/assets/sass/backlogs/_index.sass | 5 ++ .../assets/sass/backlogs/_master_backlog.sass | 83 +++++++++++++++++ .../src/global_styles/primer/_overrides.sass | 14 ++- .../backlogs/backlog_component.html.erb | 2 +- .../components/backlogs/backlog_component.rb | 2 +- .../backlog_header_component.html.erb | 82 +++++++---------- .../backlogs/backlog_header_component.rb | 2 +- .../backlogs/collapsible_component.html.erb | 61 +++++++++++++ .../backlogs/collapsible_component.rb | 90 +++++++++++++++++++ .../backlogs/sprint_page_header_component.rb | 1 - .../backlogs/story_component.html.erb | 26 ++++-- .../components/backlogs/story_component.rb | 2 +- modules/backlogs/config/locales/en.yml | 4 + .../backlogs/backlog_component_spec.rb | 2 +- .../backlogs/backlog_header_component_spec.rb | 2 +- 16 files changed, 309 insertions(+), 110 deletions(-) delete mode 100644 app/components/primer/component_helpers.rb create mode 100644 modules/backlogs/app/components/backlogs/collapsible_component.html.erb create mode 100644 modules/backlogs/app/components/backlogs/collapsible_component.rb diff --git a/app/components/primer/component_helpers.rb b/app/components/primer/component_helpers.rb deleted file mode 100644 index 603fde56b4d..00000000000 --- a/app/components/primer/component_helpers.rb +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true - -#-- copyright -# OpenProject is an open source project management software. -# Copyright (C) the OpenProject GmbH -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License version 3. -# -# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: -# Copyright (C) 2006-2013 Jean-Philippe Lang -# Copyright (C) 2010-2013 the ChiliProject Team -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# See COPYRIGHT and LICENSE files for more details. -#++ - -module Primer - module ComponentHelpers - def stack(**, &) - render(Primer::Alpha::Stack.new(**), &) - end - - def stack_item(**, &) - render(Primer::Alpha::StackItem.new(**), &) - end - end -end diff --git a/frontend/src/assets/sass/backlogs/_index.sass b/frontend/src/assets/sass/backlogs/_index.sass index bb1eb5d44a7..0f2b73435f4 100644 --- a/frontend/src/assets/sass/backlogs/_index.sass +++ b/frontend/src/assets/sass/backlogs/_index.sass @@ -33,6 +33,11 @@ * See COPYRIGHT and LICENSE files for more details. */ +// Variables +@import "../../../global_styles/openproject/_variable_defaults.scss" + +@import "../../../global_styles/openproject/_variables.sass" + @import global @import global_print @import jqplot diff --git a/frontend/src/assets/sass/backlogs/_master_backlog.sass b/frontend/src/assets/sass/backlogs/_master_backlog.sass index 576744dfda7..a69e83b511d 100644 --- a/frontend/src/assets/sass/backlogs/_master_backlog.sass +++ b/frontend/src/assets/sass/backlogs/_master_backlog.sass @@ -31,3 +31,86 @@ li[data-empty-list-item] li[data-empty-list-item]:only-child display: list-item + +$op-backlogs-header--points-min-width: 5rem + +.op-backlogs-header + display: grid + grid-template-columns: 1fr minmax($op-backlogs-header--points-min-width, max-content) auto + grid-template-areas: "collapsible points menu" + align-items: center + +.op-backlogs-header--collapsible + margin-left: calc(var(--stack-padding-normal) / 2) + +.op-backlogs-header--points + margin-left: var(--stack-gap-normal) + +.op-backlogs-header--menu + margin-left: var(--stack-gap-normal) + +.op-backlogs-collapsible + display: flex + flex-wrap: flex + align-items: center + column-gap: var(--stack-gap-normal) + row-gap: var(--base-size-4) + + &--title-line + display: flex + align-items: center + gap: var(--stack-gap-condensed) + flex: 1 + min-width: fit-content + + &--description + display: inline + white-space: nowrap + + &--toggle + svg + transition: opacity 120ms ease + transform-origin: center + + &[hidden] + opacity: 0 + +@media screen and (max-width: $breakpoint-sm) + .op-backlogs-collapsible + flex-direction: column + align-items: flex-start + + &--description + [data-collapsed] & + display: none + +.op-backlogs-story + display: grid + grid-template-columns: var(--control-xsmall-size) 1fr minmax($op-backlogs-header--points-min-width, max-content) auto + grid-template-rows: auto auto + grid-template-areas: "drag_handle info_line points menu" "drag_handle subject subject subject" + align-items: center + margin-bottom: var(--base-size-4) + +.op-backlogs-story--drag_handle + align-self: start + display: flex + padding-top: var(--base-size-8) + +.op-backlogs-story--drag_handle_button + padding: var(--base-size-4) + +.op-backlogs-story--info_line + align-self: end + margin-bottom: var(--base-size-4) + +.op-backlogs-story--points + margin-left: var(--stack-gap-normal) + +.op-backlogs-story--menu + margin-left: var(--stack-gap-normal) + +.op-backlogs-story--subject + align-self: start // Align to top of second row + word-wrap: break-word + overflow-wrap: break-word diff --git a/frontend/src/global_styles/primer/_overrides.sass b/frontend/src/global_styles/primer/_overrides.sass index f5c91030591..c79dc6a82b4 100644 --- a/frontend/src/global_styles/primer/_overrides.sass +++ b/frontend/src/global_styles/primer/_overrides.sass @@ -160,8 +160,14 @@ ul.SegmentedControl, .Box-row--clickable cursor: pointer -.Box-row--draggable - padding-left: calc(var(--stack-padding-normal) / 2) +.Box-row:is(.Box-row--draggable) + padding-left: 0 -.Box-header--collapsible - padding-left: calc(var(--stack-padding-normal) / 2) + .DragHandle + visibility: hidden + + &:hover, + &:focus-visible, + &:focus-within + .DragHandle + visibility: visible diff --git a/modules/backlogs/app/components/backlogs/backlog_component.html.erb b/modules/backlogs/app/components/backlogs/backlog_component.html.erb index bca17b3bff1..05f5c412acb 100644 --- a/modules/backlogs/app/components/backlogs/backlog_component.html.erb +++ b/modules/backlogs/app/components/backlogs/backlog_component.html.erb @@ -29,7 +29,7 @@ See COPYRIGHT and LICENSE files for more details. <%= component_wrapper(tag: :section) do %> <%= render(Primer::Beta::BorderBox.new(**@system_arguments)) do |border_box| %> - <% border_box.with_header(classes: "Box-header--collapsible") do %> + <% border_box.with_header do %> <%= render(Backlogs::BacklogHeaderComponent.new(backlog:, project: @project, folded: folded?)) %> <% end %> <% border_box.with_row(data: { empty_list_item: true }) do %> diff --git a/modules/backlogs/app/components/backlogs/backlog_component.rb b/modules/backlogs/app/components/backlogs/backlog_component.rb index e313299ba64..2b72005c4fc 100644 --- a/modules/backlogs/app/components/backlogs/backlog_component.rb +++ b/modules/backlogs/app/components/backlogs/backlog_component.rb @@ -31,7 +31,6 @@ module Backlogs class BacklogComponent < ApplicationComponent include Primer::AttributesHelper - include Primer::ComponentHelpers include OpTurbo::Streamable include RbCommonHelper @@ -49,6 +48,7 @@ module Backlogs @system_arguments = system_arguments @system_arguments[:id] = dom_id(backlog) @system_arguments[:list_id] = "#{@system_arguments[:id]}-list" + @system_arguments[:padding] = :condensed @system_arguments[:data] = merge_data( @system_arguments, { data: drop_target_config } diff --git a/modules/backlogs/app/components/backlogs/backlog_header_component.html.erb b/modules/backlogs/app/components/backlogs/backlog_header_component.html.erb index 05729bfb7ce..50cf8503479 100644 --- a/modules/backlogs/app/components/backlogs/backlog_header_component.html.erb +++ b/modules/backlogs/app/components/backlogs/backlog_header_component.html.erb @@ -29,66 +29,50 @@ See COPYRIGHT and LICENSE files for more details. <%= component_wrapper(tag: :header) do %> <% if show? %> - <%= - stack( - tag: :"collapsible-header", - direction: :horizontal, - align: :center, - justify: :space_between, - classes: class_names( - "CollapsibleHeader", - "CollapsibleHeader--collapsed" => @collapsed - ), - data: { collapsed: (@collapsed if @collapsed) } - ) do - %> - <%= stack_item(classes: "hide-when-print") do %> + <%= grid_layout("op-backlogs-header", tag: :div) do |grid| %> + <% grid.with_area(:collapsible) do %> <%= render( - Primer::BaseComponent.new( - tag: :div, - role: "button", - tabindex: 0, - classes: "CollapsibleSection--triggerArea", + Backlogs::CollapsibleComponent.new( + collapsible_id: "#{dom_id(backlog)}-list", + toggle_label: t(".label_toggle_backlog", name: sprint.name), + collapsed: + ) + ) do |collapsible| + collapsible.with_title { sprint.name } + collapsible.with_count( + scheme: :default, + count: story_count, + round: true, aria: { - label: t(".label_toggle_backlog", name: sprint.name), - controls: "#{dom_id(backlog)}-list", - expanded: !@collapsed - }, - data: { - target: "collapsible-header.triggerElement", - action: "click:collapsible-header#toggle keydown:collapsible-header#toggleViaKeyboard" + label: t(".label_story_count", count: story_count), + live: "polite" } ) + collapsible.with_description(role: "group") do + format_date_range(date_range) + end + end + %> + <% end %> + + <% grid.with_area(:points) do %> + <%= + render( + Primer::Beta::Truncate.new( + color: :subtle, + classes: "velocity", + aria: { live: "polite" } + ) ) do %> - <%= render(Primer::Beta::Octicon.new(icon: "chevron-up", hidden: @collapsed, data: { target: "collapsible-header.arrowUp" })) %> - <%= render(Primer::Beta::Octicon.new(icon: "chevron-down", hidden: !@collapsed, data: { target: "collapsible-header.arrowDown" })) %> - <% end %> - <% end %> - <%= stack_item(grow: true) do %> - <%= stack(direction: :horizontal, align: :center) do %> - <%= render Primer::Beta::Truncate.new(tag: :h4, classes: "Box-title") do %> - <%= sprint.name %> - <% end %> - - <%= stack_item do %> - <%= render(Primer::Beta::Counter.new(count: story_count, round: true)) %> - <% end %> - - <%= stack_item(grow: true) do %> - <%= render(Primer::Beta::Text.new(color: :subtle, role: "group")) do %> - <%= format_date_range(date_range) %> - <% end %> - <% end %> + <%= t(:"backlogs.points", count: story_points) %> <% end %> <% end %> - <%= render(Primer::Beta::Truncate.new(color: :subtle, classes: "velocity")) do %> - <%= t(:"backlogs.points", count: story_points) %> + <% grid.with_area(:menu) do %> + <%= render(Backlogs::BacklogMenuComponent.new(backlog:, project: @project)) %> <% end %> - - <%= render(Backlogs::BacklogMenuComponent.new(backlog:, project: @project)) %> <% end %> <% else %> <%= diff --git a/modules/backlogs/app/components/backlogs/backlog_header_component.rb b/modules/backlogs/app/components/backlogs/backlog_header_component.rb index 0e73c686412..fd81a433cb7 100644 --- a/modules/backlogs/app/components/backlogs/backlog_header_component.rb +++ b/modules/backlogs/app/components/backlogs/backlog_header_component.rb @@ -30,8 +30,8 @@ module Backlogs class BacklogHeaderComponent < ApplicationComponent + include OpPrimer::ComponentHelpers include OpTurbo::Streamable - include Primer::ComponentHelpers include Redmine::I18n include RbCommonHelper diff --git a/modules/backlogs/app/components/backlogs/collapsible_component.html.erb b/modules/backlogs/app/components/backlogs/collapsible_component.html.erb new file mode 100644 index 00000000000..78e27123b5d --- /dev/null +++ b/modules/backlogs/app/components/backlogs/collapsible_component.html.erb @@ -0,0 +1,61 @@ +<%# -- 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. + +++# %> + +<%= render(Primer::BaseComponent.new(**@system_arguments)) do %> + <%= render(Primer::BaseComponent.new(tag: :div, classes: "op-backlogs-collapsible")) do %> + <%= render(Primer::BaseComponent.new(tag: :div, classes: "op-backlogs-collapsible--title-line")) do %> + <%= title %> + <%= count %> + <%= + render( + Primer::BaseComponent.new( + tag: :div, + role: "button", + tabindex: 0, + classes: "op-backlogs-collapsible--toggle CollapsibleSection--triggerArea", + aria: { + label: @toggle_label, + controls: @collapsible_id, + expanded: !@collapsed + }, + data: { + target: "collapsible-header.triggerElement", + action: "click:collapsible-header#toggle keydown:collapsible-header#toggleViaKeyboard" + } + ) + ) do + %> + <%= render(Primer::Beta::Octicon.new(icon: "chevron-up", hidden: @collapsed, data: { target: "collapsible-header.arrowUp" })) %> + <%= render(Primer::Beta::Octicon.new(icon: "chevron-down", hidden: !@collapsed, data: { target: "collapsible-header.arrowDown" })) %> + <% end %> + <% end %> + + <%= description %> + <% end %> +<% end %> diff --git a/modules/backlogs/app/components/backlogs/collapsible_component.rb b/modules/backlogs/app/components/backlogs/collapsible_component.rb new file mode 100644 index 00000000000..3ab04940271 --- /dev/null +++ b/modules/backlogs/app/components/backlogs/collapsible_component.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module Backlogs + class CollapsibleComponent < Primer::Component + include OpPrimer::ComponentHelpers + + renders_one :title, ->(**system_arguments) { + system_arguments[:classes] = class_names( + system_arguments[:classes], + "op-backlogs-collapsible--title", + "Box-title" + ) + + Primer::Beta::Truncate.new(tag: :h3, **system_arguments) + } + + renders_one :count, ->(**system_arguments) { + system_arguments[:mr] ||= 2 + system_arguments[:scheme] ||= :primary + system_arguments[:classes] = class_names( + system_arguments[:classes], + "op-backlogs-collapsible--count" + ) + + Primer::Beta::Counter.new(**system_arguments) + } + + renders_one :description, ->(**system_arguments) { + system_arguments[:color] ||= :subtle + system_arguments[:hidden] = @collapsed + system_arguments[:classes] = class_names( + system_arguments[:classes], + "op-backlogs-collapsible--description" + ) + + Primer::Beta::Text.new(**system_arguments) + } + + def initialize(collapsible_id:, toggle_label:, collapsed: false, **system_arguments) + super() + + @collapsible_id = collapsible_id + @toggle_label = toggle_label + @collapsed = collapsed + + @system_arguments = deny_tag_argument(**system_arguments) + @system_arguments[:tag] = :"collapsible-header" + @system_arguments[:classes] = class_names( + system_arguments[:classes], + "CollapsibleHeader", + "CollapsibleHeader--collapsed" => @collapsed + ) + if @collapsed + @system_arguments[:data] = merge_data( + @system_arguments, { + data: { collapsed: @collapsed } + } + ) + end + end + end +end diff --git a/modules/backlogs/app/components/backlogs/sprint_page_header_component.rb b/modules/backlogs/app/components/backlogs/sprint_page_header_component.rb index 3fcba09e618..c3e0fdb1204 100644 --- a/modules/backlogs/app/components/backlogs/sprint_page_header_component.rb +++ b/modules/backlogs/app/components/backlogs/sprint_page_header_component.rb @@ -30,7 +30,6 @@ module Backlogs class SprintPageHeaderComponent < ApplicationComponent - include Primer::ComponentHelpers include ApplicationHelper include RbCommonHelper diff --git a/modules/backlogs/app/components/backlogs/story_component.html.erb b/modules/backlogs/app/components/backlogs/story_component.html.erb index d9cbaf2a9bc..a71589f147b 100644 --- a/modules/backlogs/app/components/backlogs/story_component.html.erb +++ b/modules/backlogs/app/components/backlogs/story_component.html.erb @@ -27,12 +27,13 @@ See COPYRIGHT and LICENSE files for more details. ++# %> -<%= stack(tag: :article, direction: :horizontal, justify: :space_between) do %> - <%= stack_item(classes: "hide-when-print") do %> +<%= grid_layout("op-backlogs-story", tag: :article) do |grid| %> + <% grid.with_area(:drag_handle, classes: "hide-when-print") do %> <%= render( Primer::OpenProject::DragHandle.new( role: "button", + classes: "op-backlogs-story--drag_handle_button", tabindex: 0, aria: { label: t(".label_drag_story", name: story.subject) @@ -42,16 +43,23 @@ See COPYRIGHT and LICENSE files for more details. %> <% end %> - <%= stack_item(grow: true) do %> + <% grid.with_area(:info_line) do %> <%= render(WorkPackages::InfoLineComponent.new(work_package: story)) %> + <% end %> + + <% grid.with_area(:points) do %> + <%= render(Primer::Beta::Truncate.new(color: :subtle, mt: 1)) do %> + <%= t(:"backlogs.points", count: story_points) %> + <% end %> + <% end %> + + <% grid.with_area(:menu) do %> + <%= render(Backlogs::StoryMenuComponent.new(story:, sprint:, max_position:)) %> + <% end %> + + <% grid.with_area(:subject) do %> <%= render(Primer::Beta::Text.new(font_weight: :semibold)) do %> <%= story.subject %> <% end %> <% end %> - - <%= render(Primer::Beta::Truncate.new(color: :subtle, mt: 1)) do %> - <%= t(:"backlogs.points", count: story_points) %> - <% end %> - - <%= render(Backlogs::StoryMenuComponent.new(story:, sprint:, max_position:)) %> <% end %> diff --git a/modules/backlogs/app/components/backlogs/story_component.rb b/modules/backlogs/app/components/backlogs/story_component.rb index 2a22543cdcd..39c37f48a7f 100644 --- a/modules/backlogs/app/components/backlogs/story_component.rb +++ b/modules/backlogs/app/components/backlogs/story_component.rb @@ -30,7 +30,7 @@ module Backlogs class StoryComponent < ApplicationComponent - include Primer::ComponentHelpers + include OpPrimer::ComponentHelpers attr_reader :story, :sprint, :max_position, :current_user diff --git a/modules/backlogs/config/locales/en.yml b/modules/backlogs/config/locales/en.yml index d6529e816a5..83f1013702e 100644 --- a/modules/backlogs/config/locales/en.yml +++ b/modules/backlogs/config/locales/en.yml @@ -107,6 +107,10 @@ en: backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" backlog_menu_component: label_actions: "Backlog actions" diff --git a/modules/backlogs/spec/components/backlogs/backlog_component_spec.rb b/modules/backlogs/spec/components/backlogs/backlog_component_spec.rb index ed362f8dae1..52951ec51b8 100644 --- a/modules/backlogs/spec/components/backlogs/backlog_component_spec.rb +++ b/modules/backlogs/spec/components/backlogs/backlog_component_spec.rb @@ -94,7 +94,7 @@ RSpec.describe Backlogs::BacklogComponent, type: :component do it "renders BacklogHeaderComponent in header" do render_component - expect(page).to have_css(".Box-header h4", text: "Sprint 1") + expect(page).to have_css(".Box-header h3", text: "Sprint 1") end it "renders StoryComponent for each story" do diff --git a/modules/backlogs/spec/components/backlogs/backlog_header_component_spec.rb b/modules/backlogs/spec/components/backlogs/backlog_header_component_spec.rb index 01023c78864..cd7e44a1d73 100644 --- a/modules/backlogs/spec/components/backlogs/backlog_header_component_spec.rb +++ b/modules/backlogs/spec/components/backlogs/backlog_header_component_spec.rb @@ -91,7 +91,7 @@ RSpec.describe Backlogs::BacklogHeaderComponent, type: :component do it "displays sprint name in h4" do render_component - expect(page).to have_css("h4", text: "Sprint 1") + expect(page).to have_css("h3", text: "Sprint 1") end it "shows story count via Primer::Beta::Counter" do From 8688dfd5c253e9f699a58d497cbfa4489423ff85 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Thu, 29 Jan 2026 14:09:44 -0300 Subject: [PATCH 073/293] Make Backlog Header form responsive Display on multiple lines on smaller viewports. --- .../assets/sass/backlogs/_master_backlog.sass | 6 ++++ .../backlog_header_component.html.erb | 3 +- .../app/forms/backlogs/backlog_header_form.rb | 28 ++++++++++--------- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/frontend/src/assets/sass/backlogs/_master_backlog.sass b/frontend/src/assets/sass/backlogs/_master_backlog.sass index a69e83b511d..385f5e5c343 100644 --- a/frontend/src/assets/sass/backlogs/_master_backlog.sass +++ b/frontend/src/assets/sass/backlogs/_master_backlog.sass @@ -114,3 +114,9 @@ $op-backlogs-header--points-min-width: 5rem align-self: start // Align to top of second row word-wrap: break-word overflow-wrap: break-word + +@media screen and (min-width: $breakpoint-sm) + .op-backlogs-header-form + .FormControl-spacingWrapper + flex-direction: row + column-gap: 0.5rem diff --git a/modules/backlogs/app/components/backlogs/backlog_header_component.html.erb b/modules/backlogs/app/components/backlogs/backlog_header_component.html.erb index 50cf8503479..b9939738804 100644 --- a/modules/backlogs/app/components/backlogs/backlog_header_component.html.erb +++ b/modules/backlogs/app/components/backlogs/backlog_header_component.html.erb @@ -79,7 +79,8 @@ See COPYRIGHT and LICENSE files for more details. primer_form_with( url: backlogs_project_sprint_path(project, sprint), model: sprint, - method: :patch + method: :patch, + class: "op-backlogs-header-form" ) do |f| render(Backlogs::BacklogHeaderForm.new(f, cancel_path: show_name_backlogs_project_sprint_path(project, sprint))) end diff --git a/modules/backlogs/app/forms/backlogs/backlog_header_form.rb b/modules/backlogs/app/forms/backlogs/backlog_header_form.rb index 0a258dd8dd7..f8b298d9411 100644 --- a/modules/backlogs/app/forms/backlogs/backlog_header_form.rb +++ b/modules/backlogs/app/forms/backlogs/backlog_header_form.rb @@ -33,17 +33,17 @@ module Backlogs attr_reader :cancel_path form do |f| - f.group(layout: :horizontal) do |group| - group.text_field( - name: :name, - label: attribute_name(:name), - placeholder: attribute_name(:name), - visually_hide_label: true, - autofocus: true, - autocomplete: "off" - ) + f.text_field( + name: :name, + label: attribute_name(:name), + placeholder: attribute_name(:name), + visually_hide_label: true, + autofocus: true, + autocomplete: "off" + ) - group.single_date_picker( + f.group(layout: :horizontal) do |dates| + dates.single_date_picker( name: :start_date, label: attribute_name(:start_date), placeholder: attribute_name(:start_date), @@ -51,7 +51,7 @@ module Backlogs leading_visual: { icon: :calendar }, datepicker_options: {} ) - group.single_date_picker( + dates.single_date_picker( name: :effective_date, label: attribute_name(:effective_date), placeholder: attribute_name(:effective_date), @@ -59,9 +59,11 @@ module Backlogs leading_visual: { icon: :calendar }, datepicker_options: {} ) + end - group.submit(scheme: :primary, name: :submit, label: I18n.t(:button_save)) - group.button( + f.group(layout: :horizontal) do |buttons| + buttons.submit(scheme: :primary, name: :submit, label: I18n.t(:button_save)) + buttons.button( scheme: :secondary, name: :cancel, label: I18n.t(:button_cancel), From cb72e00d2b5f13d44b999f2faa8488c5ea699ad1 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Thu, 29 Jan 2026 15:17:08 -0300 Subject: [PATCH 074/293] Remove unused jquery.flot vendored/npm dependency --- frontend/package-lock.json | 11 - frontend/package.json | 1 - frontend/src/vendor/jquery.flot/LICENSE | 22 - frontend/src/vendor/jquery.flot/excanvas.js | 1427 --------- .../src/vendor/jquery.flot/excanvas.min.js | 1 - .../vendor/jquery.flot/jquery.colorhelpers.js | 179 -- .../jquery.flot/jquery.colorhelpers.min.js | 1 - .../jquery.flot/jquery.flot.crosshair.js | 167 -- .../jquery.flot/jquery.flot.crosshair.min.js | 1 - .../jquery.flot/jquery.flot.fillbetween.js | 183 -- .../jquery.flot.fillbetween.min.js | 1 - .../vendor/jquery.flot/jquery.flot.image.js | 238 -- .../jquery.flot/jquery.flot.image.min.js | 1 - .../src/vendor/jquery.flot/jquery.flot.js | 2599 ----------------- .../src/vendor/jquery.flot/jquery.flot.min.js | 6 - .../jquery.flot/jquery.flot.navigate.js | 336 --- .../jquery.flot/jquery.flot.navigate.min.js | 1 - .../src/vendor/jquery.flot/jquery.flot.pie.js | 748 ----- .../vendor/jquery.flot/jquery.flot.pie.min.js | 1 - .../vendor/jquery.flot/jquery.flot.resize.js | 60 - .../jquery.flot/jquery.flot.resize.min.js | 1 - .../jquery.flot/jquery.flot.selection.js | 344 --- .../jquery.flot/jquery.flot.selection.min.js | 1 - .../vendor/jquery.flot/jquery.flot.stack.js | 184 -- .../jquery.flot/jquery.flot.stack.min.js | 1 - .../vendor/jquery.flot/jquery.flot.symbol.js | 70 - .../jquery.flot/jquery.flot.symbol.min.js | 1 - .../jquery.flot/jquery.flot.threshold.js | 103 - .../jquery.flot/jquery.flot.threshold.min.js | 1 - 29 files changed, 6690 deletions(-) delete mode 100644 frontend/src/vendor/jquery.flot/LICENSE delete mode 100644 frontend/src/vendor/jquery.flot/excanvas.js delete mode 100644 frontend/src/vendor/jquery.flot/excanvas.min.js delete mode 100644 frontend/src/vendor/jquery.flot/jquery.colorhelpers.js delete mode 100644 frontend/src/vendor/jquery.flot/jquery.colorhelpers.min.js delete mode 100644 frontend/src/vendor/jquery.flot/jquery.flot.crosshair.js delete mode 100644 frontend/src/vendor/jquery.flot/jquery.flot.crosshair.min.js delete mode 100644 frontend/src/vendor/jquery.flot/jquery.flot.fillbetween.js delete mode 100644 frontend/src/vendor/jquery.flot/jquery.flot.fillbetween.min.js delete mode 100644 frontend/src/vendor/jquery.flot/jquery.flot.image.js delete mode 100644 frontend/src/vendor/jquery.flot/jquery.flot.image.min.js delete mode 100644 frontend/src/vendor/jquery.flot/jquery.flot.js delete mode 100644 frontend/src/vendor/jquery.flot/jquery.flot.min.js delete mode 100644 frontend/src/vendor/jquery.flot/jquery.flot.navigate.js delete mode 100644 frontend/src/vendor/jquery.flot/jquery.flot.navigate.min.js delete mode 100644 frontend/src/vendor/jquery.flot/jquery.flot.pie.js delete mode 100644 frontend/src/vendor/jquery.flot/jquery.flot.pie.min.js delete mode 100644 frontend/src/vendor/jquery.flot/jquery.flot.resize.js delete mode 100644 frontend/src/vendor/jquery.flot/jquery.flot.resize.min.js delete mode 100644 frontend/src/vendor/jquery.flot/jquery.flot.selection.js delete mode 100644 frontend/src/vendor/jquery.flot/jquery.flot.selection.min.js delete mode 100644 frontend/src/vendor/jquery.flot/jquery.flot.stack.js delete mode 100644 frontend/src/vendor/jquery.flot/jquery.flot.stack.min.js delete mode 100644 frontend/src/vendor/jquery.flot/jquery.flot.symbol.js delete mode 100644 frontend/src/vendor/jquery.flot/jquery.flot.symbol.min.js delete mode 100644 frontend/src/vendor/jquery.flot/jquery.flot.threshold.js delete mode 100644 frontend/src/vendor/jquery.flot/jquery.flot.threshold.min.js diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4dc0f2c2dbd..1d4491fcc6a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -92,7 +92,6 @@ "jquery": "^3.7.1", "jquery.caret": "^0.3.1", "jquery.cookie": "^1.4.1", - "jquery.flot": "^0.8.3", "json5": "^2.2.2", "lit-html": "^3.3.2", "lodash": "^4.17.23", @@ -16957,11 +16956,6 @@ "integrity": "sha512-c/hZOOL+8VSw/FkTVH637gS1/6YzMSCROpTZ2qBYwJ7s7sHajU7uBkSSiE5+GXWwrfCCyO+jsYjUQ7Hs2rIxAA==", "license": "MIT" }, - "node_modules/jquery.flot": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/jquery.flot/-/jquery.flot-0.8.3.tgz", - "integrity": "sha512-/tEE8J5NjwvStHDaCHkvTJpD7wDS4hE1OEL8xEmhgQfUe0gLUem923PIceNez1mz4yBNx6Hjv7pJcowLNd+nbg==" - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -36585,11 +36579,6 @@ "resolved": "https://registry.npmjs.org/jquery.cookie/-/jquery.cookie-1.4.1.tgz", "integrity": "sha512-c/hZOOL+8VSw/FkTVH637gS1/6YzMSCROpTZ2qBYwJ7s7sHajU7uBkSSiE5+GXWwrfCCyO+jsYjUQ7Hs2rIxAA==" }, - "jquery.flot": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/jquery.flot/-/jquery.flot-0.8.3.tgz", - "integrity": "sha512-/tEE8J5NjwvStHDaCHkvTJpD7wDS4hE1OEL8xEmhgQfUe0gLUem923PIceNez1mz4yBNx6Hjv7pJcowLNd+nbg==" - }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 18a4f4d79f6..1987cf81831 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -147,7 +147,6 @@ "jquery": "^3.7.1", "jquery.caret": "^0.3.1", "jquery.cookie": "^1.4.1", - "jquery.flot": "^0.8.3", "json5": "^2.2.2", "lit-html": "^3.3.2", "lodash": "^4.17.23", diff --git a/frontend/src/vendor/jquery.flot/LICENSE b/frontend/src/vendor/jquery.flot/LICENSE deleted file mode 100644 index 07d5b2094d1..00000000000 --- a/frontend/src/vendor/jquery.flot/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -Copyright (c) 2007-2009 IOLA and Ole Laursen - -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without -restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. diff --git a/frontend/src/vendor/jquery.flot/excanvas.js b/frontend/src/vendor/jquery.flot/excanvas.js deleted file mode 100644 index c40d6f7014d..00000000000 --- a/frontend/src/vendor/jquery.flot/excanvas.js +++ /dev/null @@ -1,1427 +0,0 @@ -// Copyright 2006 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - - -// Known Issues: -// -// * Patterns only support repeat. -// * Radial gradient are not implemented. The VML version of these look very -// different from the canvas one. -// * Clipping paths are not implemented. -// * Coordsize. The width and height attribute have higher priority than the -// width and height style values which isn't correct. -// * Painting mode isn't implemented. -// * Canvas width/height should is using content-box by default. IE in -// Quirks mode will draw the canvas using border-box. Either change your -// doctype to HTML5 -// (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype) -// or use Box Sizing Behavior from WebFX -// (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html) -// * Non uniform scaling does not correctly scale strokes. -// * Filling very large shapes (above 5000 points) is buggy. -// * Optimize. There is always room for speed improvements. - -// Only add this code if we do not already have a canvas implementation -if (!document.createElement('canvas').getContext) { - -(function() { - - // alias some functions to make (compiled) code shorter - var m = Math; - var mr = m.round; - var ms = m.sin; - var mc = m.cos; - var abs = m.abs; - var sqrt = m.sqrt; - - // this is used for sub pixel precision - var Z = 10; - var Z2 = Z / 2; - - /** - * This funtion is assigned to the elements as element.getContext(). - * @this {HTMLElement} - * @return {CanvasRenderingContext2D_} - */ - function getContext() { - return this.context_ || - (this.context_ = new CanvasRenderingContext2D_(this)); - } - - var slice = Array.prototype.slice; - - /** - * Binds a function to an object. The returned function will always use the - * passed in {@code obj} as {@code this}. - * - * Example: - * - * g = bind(f, obj, a, b) - * g(c, d) // will do f.call(obj, a, b, c, d) - * - * @param {Function} f The function to bind the object to - * @param {Object} obj The object that should act as this when the function - * is called - * @param {*} var_args Rest arguments that will be used as the initial - * arguments when the function is called - * @return {Function} A new function that has bound this - */ - function bind(f, obj, var_args) { - var a = slice.call(arguments, 2); - return function() { - return f.apply(obj, a.concat(slice.call(arguments))); - }; - } - - function encodeHtmlAttribute(s) { - return String(s).replace(/&/g, '&').replace(/"/g, '"'); - } - - function addNamespacesAndStylesheet(doc) { - // create xmlns - if (!doc.namespaces['g_vml_']) { - doc.namespaces.add('g_vml_', 'urn:schemas-microsoft-com:vml', - '#default#VML'); - - } - if (!doc.namespaces['g_o_']) { - doc.namespaces.add('g_o_', 'urn:schemas-microsoft-com:office:office', - '#default#VML'); - } - - // Setup default CSS. Only add one style sheet per document - if (!doc.styleSheets['ex_canvas_']) { - var ss = doc.createStyleSheet(); - ss.owningElement.id = 'ex_canvas_'; - ss.cssText = 'canvas{display:inline-block;overflow:hidden;' + - // default size is 300x150 in Gecko and Opera - 'text-align:left;width:300px;height:150px}'; - } - } - - // Add namespaces and stylesheet at startup. - addNamespacesAndStylesheet(document); - - var G_vmlCanvasManager_ = { - init: function(opt_doc) { - if (/MSIE/.test(navigator.userAgent) && !window.opera) { - var doc = opt_doc || document; - // Create a dummy element so that IE will allow canvas elements to be - // recognized. - doc.createElement('canvas'); - doc.attachEvent('onreadystatechange', bind(this.init_, this, doc)); - } - }, - - init_: function(doc) { - // find all canvas elements - var els = doc.getElementsByTagName('canvas'); - for (var i = 0; i < els.length; i++) { - this.initElement(els[i]); - } - }, - - /** - * Public initializes a canvas element so that it can be used as canvas - * element from now on. This is called automatically before the page is - * loaded but if you are creating elements using createElement you need to - * make sure this is called on the element. - * @param {HTMLElement} el The canvas element to initialize. - * @return {HTMLElement} the element that was created. - */ - initElement: function(el) { - if (!el.getContext) { - el.getContext = getContext; - - // Add namespaces and stylesheet to document of the element. - addNamespacesAndStylesheet(el.ownerDocument); - - // Remove fallback content. There is no way to hide text nodes so we - // just remove all childNodes. We could hide all elements and remove - // text nodes but who really cares about the fallback content. - el.innerHTML = ''; - - // do not use inline function because that will leak memory - el.attachEvent('onpropertychange', onPropertyChange); - el.attachEvent('onresize', onResize); - - var attrs = el.attributes; - if (attrs.width && attrs.width.specified) { - // TODO: use runtimeStyle and coordsize - // el.getContext().setWidth_(attrs.width.nodeValue); - el.style.width = attrs.width.nodeValue + 'px'; - } else { - el.width = el.clientWidth; - } - if (attrs.height && attrs.height.specified) { - // TODO: use runtimeStyle and coordsize - // el.getContext().setHeight_(attrs.height.nodeValue); - el.style.height = attrs.height.nodeValue + 'px'; - } else { - el.height = el.clientHeight; - } - //el.getContext().setCoordsize_() - } - return el; - } - }; - - function onPropertyChange(e) { - var el = e.srcElement; - - switch (e.propertyName) { - case 'width': - el.getContext().clearRect(); - el.style.width = el.attributes.width.nodeValue + 'px'; - // In IE8 this does not trigger onresize. - el.firstChild.style.width = el.clientWidth + 'px'; - break; - case 'height': - el.getContext().clearRect(); - el.style.height = el.attributes.height.nodeValue + 'px'; - el.firstChild.style.height = el.clientHeight + 'px'; - break; - } - } - - function onResize(e) { - var el = e.srcElement; - if (el.firstChild) { - el.firstChild.style.width = el.clientWidth + 'px'; - el.firstChild.style.height = el.clientHeight + 'px'; - } - } - - G_vmlCanvasManager_.init(); - - // precompute "00" to "FF" - var decToHex = []; - for (var i = 0; i < 16; i++) { - for (var j = 0; j < 16; j++) { - decToHex[i * 16 + j] = i.toString(16) + j.toString(16); - } - } - - function createMatrixIdentity() { - return [ - [1, 0, 0], - [0, 1, 0], - [0, 0, 1] - ]; - } - - function matrixMultiply(m1, m2) { - var result = createMatrixIdentity(); - - for (var x = 0; x < 3; x++) { - for (var y = 0; y < 3; y++) { - var sum = 0; - - for (var z = 0; z < 3; z++) { - sum += m1[x][z] * m2[z][y]; - } - - result[x][y] = sum; - } - } - return result; - } - - function copyState(o1, o2) { - o2.fillStyle = o1.fillStyle; - o2.lineCap = o1.lineCap; - o2.lineJoin = o1.lineJoin; - o2.lineWidth = o1.lineWidth; - o2.miterLimit = o1.miterLimit; - o2.shadowBlur = o1.shadowBlur; - o2.shadowColor = o1.shadowColor; - o2.shadowOffsetX = o1.shadowOffsetX; - o2.shadowOffsetY = o1.shadowOffsetY; - o2.strokeStyle = o1.strokeStyle; - o2.globalAlpha = o1.globalAlpha; - o2.font = o1.font; - o2.textAlign = o1.textAlign; - o2.textBaseline = o1.textBaseline; - o2.arcScaleX_ = o1.arcScaleX_; - o2.arcScaleY_ = o1.arcScaleY_; - o2.lineScale_ = o1.lineScale_; - } - - var colorData = { - aliceblue: '#F0F8FF', - antiquewhite: '#FAEBD7', - aquamarine: '#7FFFD4', - azure: '#F0FFFF', - beige: '#F5F5DC', - bisque: '#FFE4C4', - black: '#000000', - blanchedalmond: '#FFEBCD', - blueviolet: '#8A2BE2', - brown: '#A52A2A', - burlywood: '#DEB887', - cadetblue: '#5F9EA0', - chartreuse: '#7FFF00', - chocolate: '#D2691E', - coral: '#FF7F50', - cornflowerblue: '#6495ED', - cornsilk: '#FFF8DC', - crimson: '#DC143C', - cyan: '#00FFFF', - darkblue: '#00008B', - darkcyan: '#008B8B', - darkgoldenrod: '#B8860B', - darkgray: '#A9A9A9', - darkgreen: '#006400', - darkgrey: '#A9A9A9', - darkkhaki: '#BDB76B', - darkmagenta: '#8B008B', - darkolivegreen: '#556B2F', - darkorange: '#FF8C00', - darkorchid: '#9932CC', - darkred: '#8B0000', - darksalmon: '#E9967A', - darkseagreen: '#8FBC8F', - darkslateblue: '#483D8B', - darkslategray: '#2F4F4F', - darkslategrey: '#2F4F4F', - darkturquoise: '#00CED1', - darkviolet: '#9400D3', - deeppink: '#FF1493', - deepskyblue: '#00BFFF', - dimgray: '#696969', - dimgrey: '#696969', - dodgerblue: '#1E90FF', - firebrick: '#B22222', - floralwhite: '#FFFAF0', - forestgreen: '#228B22', - gainsboro: '#DCDCDC', - ghostwhite: '#F8F8FF', - gold: '#FFD700', - goldenrod: '#DAA520', - grey: '#808080', - greenyellow: '#ADFF2F', - honeydew: '#F0FFF0', - hotpink: '#FF69B4', - indianred: '#CD5C5C', - indigo: '#4B0082', - ivory: '#FFFFF0', - khaki: '#F0E68C', - lavender: '#E6E6FA', - lavenderblush: '#FFF0F5', - lawngreen: '#7CFC00', - lemonchiffon: '#FFFACD', - lightblue: '#ADD8E6', - lightcoral: '#F08080', - lightcyan: '#E0FFFF', - lightgoldenrodyellow: '#FAFAD2', - lightgreen: '#90EE90', - lightgrey: '#D3D3D3', - lightpink: '#FFB6C1', - lightsalmon: '#FFA07A', - lightseagreen: '#20B2AA', - lightskyblue: '#87CEFA', - lightslategray: '#778899', - lightslategrey: '#778899', - lightsteelblue: '#B0C4DE', - lightyellow: '#FFFFE0', - limegreen: '#32CD32', - linen: '#FAF0E6', - magenta: '#FF00FF', - mediumaquamarine: '#66CDAA', - mediumblue: '#0000CD', - mediumorchid: '#BA55D3', - mediumpurple: '#9370DB', - mediumseagreen: '#3CB371', - mediumslateblue: '#7B68EE', - mediumspringgreen: '#00FA9A', - mediumturquoise: '#48D1CC', - mediumvioletred: '#C71585', - midnightblue: '#191970', - mintcream: '#F5FFFA', - mistyrose: '#FFE4E1', - moccasin: '#FFE4B5', - navajowhite: '#FFDEAD', - oldlace: '#FDF5E6', - olivedrab: '#6B8E23', - orange: '#FFA500', - orangered: '#FF4500', - orchid: '#DA70D6', - palegoldenrod: '#EEE8AA', - palegreen: '#98FB98', - paleturquoise: '#AFEEEE', - palevioletred: '#DB7093', - papayawhip: '#FFEFD5', - peachpuff: '#FFDAB9', - peru: '#CD853F', - pink: '#FFC0CB', - plum: '#DDA0DD', - powderblue: '#B0E0E6', - rosybrown: '#BC8F8F', - royalblue: '#4169E1', - saddlebrown: '#8B4513', - salmon: '#FA8072', - sandybrown: '#F4A460', - seagreen: '#2E8B57', - seashell: '#FFF5EE', - sienna: '#A0522D', - skyblue: '#87CEEB', - slateblue: '#6A5ACD', - slategray: '#708090', - slategrey: '#708090', - snow: '#FFFAFA', - springgreen: '#00FF7F', - steelblue: '#4682B4', - tan: '#D2B48C', - thistle: '#D8BFD8', - tomato: '#FF6347', - turquoise: '#40E0D0', - violet: '#EE82EE', - wheat: '#F5DEB3', - whitesmoke: '#F5F5F5', - yellowgreen: '#9ACD32' - }; - - - function getRgbHslContent(styleString) { - var start = styleString.indexOf('(', 3); - var end = styleString.indexOf(')', start + 1); - var parts = styleString.substring(start + 1, end).split(','); - // add alpha if needed - if (parts.length == 4 && styleString.substr(3, 1) == 'a') { - alpha = Number(parts[3]); - } else { - parts[3] = 1; - } - return parts; - } - - function percent(s) { - return parseFloat(s) / 100; - } - - function clamp(v, min, max) { - return Math.min(max, Math.max(min, v)); - } - - function hslToRgb(parts){ - var r, g, b; - h = parseFloat(parts[0]) / 360 % 360; - if (h < 0) - h++; - s = clamp(percent(parts[1]), 0, 1); - l = clamp(percent(parts[2]), 0, 1); - if (s == 0) { - r = g = b = l; // achromatic - } else { - var q = l < 0.5 ? l * (1 + s) : l + s - l * s; - var p = 2 * l - q; - r = hueToRgb(p, q, h + 1 / 3); - g = hueToRgb(p, q, h); - b = hueToRgb(p, q, h - 1 / 3); - } - - return '#' + decToHex[Math.floor(r * 255)] + - decToHex[Math.floor(g * 255)] + - decToHex[Math.floor(b * 255)]; - } - - function hueToRgb(m1, m2, h) { - if (h < 0) - h++; - if (h > 1) - h--; - - if (6 * h < 1) - return m1 + (m2 - m1) * 6 * h; - else if (2 * h < 1) - return m2; - else if (3 * h < 2) - return m1 + (m2 - m1) * (2 / 3 - h) * 6; - else - return m1; - } - - function processStyle(styleString) { - var str, alpha = 1; - - styleString = String(styleString); - if (styleString.charAt(0) == '#') { - str = styleString; - } else if (/^rgb/.test(styleString)) { - var parts = getRgbHslContent(styleString); - var str = '#', n; - for (var i = 0; i < 3; i++) { - if (parts[i].indexOf('%') != -1) { - n = Math.floor(percent(parts[i]) * 255); - } else { - n = Number(parts[i]); - } - str += decToHex[clamp(n, 0, 255)]; - } - alpha = parts[3]; - } else if (/^hsl/.test(styleString)) { - var parts = getRgbHslContent(styleString); - str = hslToRgb(parts); - alpha = parts[3]; - } else { - str = colorData[styleString] || styleString; - } - return {color: str, alpha: alpha}; - } - - var DEFAULT_STYLE = { - style: 'normal', - variant: 'normal', - weight: 'normal', - size: 10, - family: 'sans-serif' - }; - - // Internal text style cache - var fontStyleCache = {}; - - function processFontStyle(styleString) { - if (fontStyleCache[styleString]) { - return fontStyleCache[styleString]; - } - - var el = document.createElement('div'); - var style = el.style; - try { - style.font = styleString; - } catch (ex) { - // Ignore failures to set to invalid font. - } - - return fontStyleCache[styleString] = { - style: style.fontStyle || DEFAULT_STYLE.style, - variant: style.fontVariant || DEFAULT_STYLE.variant, - weight: style.fontWeight || DEFAULT_STYLE.weight, - size: style.fontSize || DEFAULT_STYLE.size, - family: style.fontFamily || DEFAULT_STYLE.family - }; - } - - function getComputedStyle(style, element) { - var computedStyle = {}; - - for (var p in style) { - computedStyle[p] = style[p]; - } - - // Compute the size - var canvasFontSize = parseFloat(element.currentStyle.fontSize), - fontSize = parseFloat(style.size); - - if (typeof style.size == 'number') { - computedStyle.size = style.size; - } else if (style.size.indexOf('px') != -1) { - computedStyle.size = fontSize; - } else if (style.size.indexOf('em') != -1) { - computedStyle.size = canvasFontSize * fontSize; - } else if(style.size.indexOf('%') != -1) { - computedStyle.size = (canvasFontSize / 100) * fontSize; - } else if (style.size.indexOf('pt') != -1) { - computedStyle.size = fontSize / .75; - } else { - computedStyle.size = canvasFontSize; - } - - // Different scaling between normal text and VML text. This was found using - // trial and error to get the same size as non VML text. - computedStyle.size *= 0.981; - - return computedStyle; - } - - function buildStyle(style) { - return style.style + ' ' + style.variant + ' ' + style.weight + ' ' + - style.size + 'px ' + style.family; - } - - function processLineCap(lineCap) { - switch (lineCap) { - case 'butt': - return 'flat'; - case 'round': - return 'round'; - case 'square': - default: - return 'square'; - } - } - - /** - * This class implements CanvasRenderingContext2D interface as described by - * the WHATWG. - * @param {HTMLElement} surfaceElement The element that the 2D context should - * be associated with - */ - function CanvasRenderingContext2D_(surfaceElement) { - this.m_ = createMatrixIdentity(); - - this.mStack_ = []; - this.aStack_ = []; - this.currentPath_ = []; - - // Canvas context properties - this.strokeStyle = '#000'; - this.fillStyle = '#000'; - - this.lineWidth = 1; - this.lineJoin = 'miter'; - this.lineCap = 'butt'; - this.miterLimit = Z * 1; - this.globalAlpha = 1; - this.font = '10px sans-serif'; - this.textAlign = 'left'; - this.textBaseline = 'alphabetic'; - this.canvas = surfaceElement; - - var el = surfaceElement.ownerDocument.createElement('div'); - el.style.width = surfaceElement.clientWidth + 'px'; - el.style.height = surfaceElement.clientHeight + 'px'; - el.style.overflow = 'hidden'; - el.style.position = 'absolute'; - surfaceElement.appendChild(el); - - this.element_ = el; - this.arcScaleX_ = 1; - this.arcScaleY_ = 1; - this.lineScale_ = 1; - } - - var contextPrototype = CanvasRenderingContext2D_.prototype; - contextPrototype.clearRect = function() { - if (this.textMeasureEl_) { - this.textMeasureEl_.removeNode(true); - this.textMeasureEl_ = null; - } - this.element_.innerHTML = ''; - }; - - contextPrototype.beginPath = function() { - // TODO: Branch current matrix so that save/restore has no effect - // as per safari docs. - this.currentPath_ = []; - }; - - contextPrototype.moveTo = function(aX, aY) { - var p = this.getCoords_(aX, aY); - this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y}); - this.currentX_ = p.x; - this.currentY_ = p.y; - }; - - contextPrototype.lineTo = function(aX, aY) { - var p = this.getCoords_(aX, aY); - this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y}); - - this.currentX_ = p.x; - this.currentY_ = p.y; - }; - - contextPrototype.bezierCurveTo = function(aCP1x, aCP1y, - aCP2x, aCP2y, - aX, aY) { - var p = this.getCoords_(aX, aY); - var cp1 = this.getCoords_(aCP1x, aCP1y); - var cp2 = this.getCoords_(aCP2x, aCP2y); - bezierCurveTo(this, cp1, cp2, p); - }; - - // Helper function that takes the already fixed cordinates. - function bezierCurveTo(self, cp1, cp2, p) { - self.currentPath_.push({ - type: 'bezierCurveTo', - cp1x: cp1.x, - cp1y: cp1.y, - cp2x: cp2.x, - cp2y: cp2.y, - x: p.x, - y: p.y - }); - self.currentX_ = p.x; - self.currentY_ = p.y; - } - - contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) { - // the following is lifted almost directly from - // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes - - var cp = this.getCoords_(aCPx, aCPy); - var p = this.getCoords_(aX, aY); - - var cp1 = { - x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_), - y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_) - }; - var cp2 = { - x: cp1.x + (p.x - this.currentX_) / 3.0, - y: cp1.y + (p.y - this.currentY_) / 3.0 - }; - - bezierCurveTo(this, cp1, cp2, p); - }; - - contextPrototype.arc = function(aX, aY, aRadius, - aStartAngle, aEndAngle, aClockwise) { - aRadius *= Z; - var arcType = aClockwise ? 'at' : 'wa'; - - var xStart = aX + mc(aStartAngle) * aRadius - Z2; - var yStart = aY + ms(aStartAngle) * aRadius - Z2; - - var xEnd = aX + mc(aEndAngle) * aRadius - Z2; - var yEnd = aY + ms(aEndAngle) * aRadius - Z2; - - // IE won't render arches drawn counter clockwise if xStart == xEnd. - if (xStart == xEnd && !aClockwise) { - xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something - // that can be represented in binary - } - - var p = this.getCoords_(aX, aY); - var pStart = this.getCoords_(xStart, yStart); - var pEnd = this.getCoords_(xEnd, yEnd); - - this.currentPath_.push({type: arcType, - x: p.x, - y: p.y, - radius: aRadius, - xStart: pStart.x, - yStart: pStart.y, - xEnd: pEnd.x, - yEnd: pEnd.y}); - - }; - - contextPrototype.rect = function(aX, aY, aWidth, aHeight) { - this.moveTo(aX, aY); - this.lineTo(aX + aWidth, aY); - this.lineTo(aX + aWidth, aY + aHeight); - this.lineTo(aX, aY + aHeight); - this.closePath(); - }; - - contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) { - var oldPath = this.currentPath_; - this.beginPath(); - - this.moveTo(aX, aY); - this.lineTo(aX + aWidth, aY); - this.lineTo(aX + aWidth, aY + aHeight); - this.lineTo(aX, aY + aHeight); - this.closePath(); - this.stroke(); - - this.currentPath_ = oldPath; - }; - - contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) { - var oldPath = this.currentPath_; - this.beginPath(); - - this.moveTo(aX, aY); - this.lineTo(aX + aWidth, aY); - this.lineTo(aX + aWidth, aY + aHeight); - this.lineTo(aX, aY + aHeight); - this.closePath(); - this.fill(); - - this.currentPath_ = oldPath; - }; - - contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) { - var gradient = new CanvasGradient_('gradient'); - gradient.x0_ = aX0; - gradient.y0_ = aY0; - gradient.x1_ = aX1; - gradient.y1_ = aY1; - return gradient; - }; - - contextPrototype.createRadialGradient = function(aX0, aY0, aR0, - aX1, aY1, aR1) { - var gradient = new CanvasGradient_('gradientradial'); - gradient.x0_ = aX0; - gradient.y0_ = aY0; - gradient.r0_ = aR0; - gradient.x1_ = aX1; - gradient.y1_ = aY1; - gradient.r1_ = aR1; - return gradient; - }; - - contextPrototype.drawImage = function(image, var_args) { - var dx, dy, dw, dh, sx, sy, sw, sh; - - // to find the original width we overide the width and height - var oldRuntimeWidth = image.runtimeStyle.width; - var oldRuntimeHeight = image.runtimeStyle.height; - image.runtimeStyle.width = 'auto'; - image.runtimeStyle.height = 'auto'; - - // get the original size - var w = image.width; - var h = image.height; - - // and remove overides - image.runtimeStyle.width = oldRuntimeWidth; - image.runtimeStyle.height = oldRuntimeHeight; - - if (arguments.length == 3) { - dx = arguments[1]; - dy = arguments[2]; - sx = sy = 0; - sw = dw = w; - sh = dh = h; - } else if (arguments.length == 5) { - dx = arguments[1]; - dy = arguments[2]; - dw = arguments[3]; - dh = arguments[4]; - sx = sy = 0; - sw = w; - sh = h; - } else if (arguments.length == 9) { - sx = arguments[1]; - sy = arguments[2]; - sw = arguments[3]; - sh = arguments[4]; - dx = arguments[5]; - dy = arguments[6]; - dw = arguments[7]; - dh = arguments[8]; - } else { - throw Error('Invalid number of arguments'); - } - - var d = this.getCoords_(dx, dy); - - var w2 = sw / 2; - var h2 = sh / 2; - - var vmlStr = []; - - var W = 10; - var H = 10; - - // For some reason that I've now forgotten, using divs didn't work - vmlStr.push(' ' , - '', - ''); - - this.element_.insertAdjacentHTML('BeforeEnd', vmlStr.join('')); - }; - - contextPrototype.stroke = function(aFill) { - var W = 10; - var H = 10; - // Divide the shape into chunks if it's too long because IE has a limit - // somewhere for how long a VML shape can be. This simple division does - // not work with fills, only strokes, unfortunately. - var chunkSize = 5000; - - var min = {x: null, y: null}; - var max = {x: null, y: null}; - - for (var j = 0; j < this.currentPath_.length; j += chunkSize) { - var lineStr = []; - var lineOpen = false; - - lineStr.push(''); - - if (!aFill) { - appendStroke(this, lineStr); - } else { - appendFill(this, lineStr, min, max); - } - - lineStr.push(''); - - this.element_.insertAdjacentHTML('beforeEnd', lineStr.join('')); - } - }; - - function appendStroke(ctx, lineStr) { - var a = processStyle(ctx.strokeStyle); - var color = a.color; - var opacity = a.alpha * ctx.globalAlpha; - var lineWidth = ctx.lineScale_ * ctx.lineWidth; - - // VML cannot correctly render a line if the width is less than 1px. - // In that case, we dilute the color to make the line look thinner. - if (lineWidth < 1) { - opacity *= lineWidth; - } - - lineStr.push( - '' - ); - } - - function appendFill(ctx, lineStr, min, max) { - var fillStyle = ctx.fillStyle; - var arcScaleX = ctx.arcScaleX_; - var arcScaleY = ctx.arcScaleY_; - var width = max.x - min.x; - var height = max.y - min.y; - if (fillStyle instanceof CanvasGradient_) { - // TODO: Gradients transformed with the transformation matrix. - var angle = 0; - var focus = {x: 0, y: 0}; - - // additional offset - var shift = 0; - // scale factor for offset - var expansion = 1; - - if (fillStyle.type_ == 'gradient') { - var x0 = fillStyle.x0_ / arcScaleX; - var y0 = fillStyle.y0_ / arcScaleY; - var x1 = fillStyle.x1_ / arcScaleX; - var y1 = fillStyle.y1_ / arcScaleY; - var p0 = ctx.getCoords_(x0, y0); - var p1 = ctx.getCoords_(x1, y1); - var dx = p1.x - p0.x; - var dy = p1.y - p0.y; - angle = Math.atan2(dx, dy) * 180 / Math.PI; - - // The angle should be a non-negative number. - if (angle < 0) { - angle += 360; - } - - // Very small angles produce an unexpected result because they are - // converted to a scientific notation string. - if (angle < 1e-6) { - angle = 0; - } - } else { - var p0 = ctx.getCoords_(fillStyle.x0_, fillStyle.y0_); - focus = { - x: (p0.x - min.x) / width, - y: (p0.y - min.y) / height - }; - - width /= arcScaleX * Z; - height /= arcScaleY * Z; - var dimension = m.max(width, height); - shift = 2 * fillStyle.r0_ / dimension; - expansion = 2 * fillStyle.r1_ / dimension - shift; - } - - // We need to sort the color stops in ascending order by offset, - // otherwise IE won't interpret it correctly. - var stops = fillStyle.colors_; - stops.sort(function(cs1, cs2) { - return cs1.offset - cs2.offset; - }); - - var length = stops.length; - var color1 = stops[0].color; - var color2 = stops[length - 1].color; - var opacity1 = stops[0].alpha * ctx.globalAlpha; - var opacity2 = stops[length - 1].alpha * ctx.globalAlpha; - - var colors = []; - for (var i = 0; i < length; i++) { - var stop = stops[i]; - colors.push(stop.offset * expansion + shift + ' ' + stop.color); - } - - // When colors attribute is used, the meanings of opacity and o:opacity2 - // are reversed. - lineStr.push(''); - } else if (fillStyle instanceof CanvasPattern_) { - if (width && height) { - var deltaLeft = -min.x; - var deltaTop = -min.y; - lineStr.push(''); - } - } else { - var a = processStyle(ctx.fillStyle); - var color = a.color; - var opacity = a.alpha * ctx.globalAlpha; - lineStr.push(''); - } - } - - contextPrototype.fill = function() { - this.stroke(true); - }; - - contextPrototype.closePath = function() { - this.currentPath_.push({type: 'close'}); - }; - - /** - * @private - */ - contextPrototype.getCoords_ = function(aX, aY) { - var m = this.m_; - return { - x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2, - y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2 - }; - }; - - contextPrototype.save = function() { - var o = {}; - copyState(this, o); - this.aStack_.push(o); - this.mStack_.push(this.m_); - this.m_ = matrixMultiply(createMatrixIdentity(), this.m_); - }; - - contextPrototype.restore = function() { - if (this.aStack_.length) { - copyState(this.aStack_.pop(), this); - this.m_ = this.mStack_.pop(); - } - }; - - function matrixIsFinite(m) { - return isFinite(m[0][0]) && isFinite(m[0][1]) && - isFinite(m[1][0]) && isFinite(m[1][1]) && - isFinite(m[2][0]) && isFinite(m[2][1]); - } - - function setM(ctx, m, updateLineScale) { - if (!matrixIsFinite(m)) { - return; - } - ctx.m_ = m; - - if (updateLineScale) { - // Get the line scale. - // Determinant of this.m_ means how much the area is enlarged by the - // transformation. So its square root can be used as a scale factor - // for width. - var det = m[0][0] * m[1][1] - m[0][1] * m[1][0]; - ctx.lineScale_ = sqrt(abs(det)); - } - } - - contextPrototype.translate = function(aX, aY) { - var m1 = [ - [1, 0, 0], - [0, 1, 0], - [aX, aY, 1] - ]; - - setM(this, matrixMultiply(m1, this.m_), false); - }; - - contextPrototype.rotate = function(aRot) { - var c = mc(aRot); - var s = ms(aRot); - - var m1 = [ - [c, s, 0], - [-s, c, 0], - [0, 0, 1] - ]; - - setM(this, matrixMultiply(m1, this.m_), false); - }; - - contextPrototype.scale = function(aX, aY) { - this.arcScaleX_ *= aX; - this.arcScaleY_ *= aY; - var m1 = [ - [aX, 0, 0], - [0, aY, 0], - [0, 0, 1] - ]; - - setM(this, matrixMultiply(m1, this.m_), true); - }; - - contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) { - var m1 = [ - [m11, m12, 0], - [m21, m22, 0], - [dx, dy, 1] - ]; - - setM(this, matrixMultiply(m1, this.m_), true); - }; - - contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) { - var m = [ - [m11, m12, 0], - [m21, m22, 0], - [dx, dy, 1] - ]; - - setM(this, m, true); - }; - - /** - * The text drawing function. - * The maxWidth argument isn't taken in account, since no browser supports - * it yet. - */ - contextPrototype.drawText_ = function(text, x, y, maxWidth, stroke) { - var m = this.m_, - delta = 1000, - left = 0, - right = delta, - offset = {x: 0, y: 0}, - lineStr = []; - - var fontStyle = getComputedStyle(processFontStyle(this.font), - this.element_); - - var fontStyleString = buildStyle(fontStyle); - - var elementStyle = this.element_.currentStyle; - var textAlign = this.textAlign.toLowerCase(); - switch (textAlign) { - case 'left': - case 'center': - case 'right': - break; - case 'end': - textAlign = elementStyle.direction == 'ltr' ? 'right' : 'left'; - break; - case 'start': - textAlign = elementStyle.direction == 'rtl' ? 'right' : 'left'; - break; - default: - textAlign = 'left'; - } - - // 1.75 is an arbitrary number, as there is no info about the text baseline - switch (this.textBaseline) { - case 'hanging': - case 'top': - offset.y = fontStyle.size / 1.75; - break; - case 'middle': - break; - default: - case null: - case 'alphabetic': - case 'ideographic': - case 'bottom': - offset.y = -fontStyle.size / 2.25; - break; - } - - switch(textAlign) { - case 'right': - left = delta; - right = 0.05; - break; - case 'center': - left = right = delta / 2; - break; - } - - var d = this.getCoords_(x + offset.x, y + offset.y); - - lineStr.push(''); - - if (stroke) { - appendStroke(this, lineStr); - } else { - // TODO: Fix the min and max params. - appendFill(this, lineStr, {x: -left, y: 0}, - {x: right, y: fontStyle.size}); - } - - var skewM = m[0][0].toFixed(3) + ',' + m[1][0].toFixed(3) + ',' + - m[0][1].toFixed(3) + ',' + m[1][1].toFixed(3) + ',0,0'; - - var skewOffset = mr(d.x / Z) + ',' + mr(d.y / Z); - - lineStr.push('', - '', - ''); - - this.element_.insertAdjacentHTML('beforeEnd', lineStr.join('')); - }; - - contextPrototype.fillText = function(text, x, y, maxWidth) { - this.drawText_(text, x, y, maxWidth, false); - }; - - contextPrototype.strokeText = function(text, x, y, maxWidth) { - this.drawText_(text, x, y, maxWidth, true); - }; - - contextPrototype.measureText = function(text) { - if (!this.textMeasureEl_) { - var s = ''; - this.element_.insertAdjacentHTML('beforeEnd', s); - this.textMeasureEl_ = this.element_.lastChild; - } - var doc = this.element_.ownerDocument; - this.textMeasureEl_.innerHTML = ''; - this.textMeasureEl_.style.font = this.font; - // Don't use innerHTML or innerText because they allow markup/whitespace. - this.textMeasureEl_.appendChild(doc.createTextNode(text)); - return {width: this.textMeasureEl_.offsetWidth}; - }; - - /******** STUBS ********/ - contextPrototype.clip = function() { - // TODO: Implement - }; - - contextPrototype.arcTo = function() { - // TODO: Implement - }; - - contextPrototype.createPattern = function(image, repetition) { - return new CanvasPattern_(image, repetition); - }; - - // Gradient / Pattern Stubs - function CanvasGradient_(aType) { - this.type_ = aType; - this.x0_ = 0; - this.y0_ = 0; - this.r0_ = 0; - this.x1_ = 0; - this.y1_ = 0; - this.r1_ = 0; - this.colors_ = []; - } - - CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) { - aColor = processStyle(aColor); - this.colors_.push({offset: aOffset, - color: aColor.color, - alpha: aColor.alpha}); - }; - - function CanvasPattern_(image, repetition) { - assertImageIsValid(image); - switch (repetition) { - case 'repeat': - case null: - case '': - this.repetition_ = 'repeat'; - break - case 'repeat-x': - case 'repeat-y': - case 'no-repeat': - this.repetition_ = repetition; - break; - default: - throwException('SYNTAX_ERR'); - } - - this.src_ = image.src; - this.width_ = image.width; - this.height_ = image.height; - } - - function throwException(s) { - throw new DOMException_(s); - } - - function assertImageIsValid(img) { - if (!img || img.nodeType != 1 || img.tagName != 'IMG') { - throwException('TYPE_MISMATCH_ERR'); - } - if (img.readyState != 'complete') { - throwException('INVALID_STATE_ERR'); - } - } - - function DOMException_(s) { - this.code = this[s]; - this.message = s +': DOM Exception ' + this.code; - } - var p = DOMException_.prototype = new Error; - p.INDEX_SIZE_ERR = 1; - p.DOMSTRING_SIZE_ERR = 2; - p.HIERARCHY_REQUEST_ERR = 3; - p.WRONG_DOCUMENT_ERR = 4; - p.INVALID_CHARACTER_ERR = 5; - p.NO_DATA_ALLOWED_ERR = 6; - p.NO_MODIFICATION_ALLOWED_ERR = 7; - p.NOT_FOUND_ERR = 8; - p.NOT_SUPPORTED_ERR = 9; - p.INUSE_ATTRIBUTE_ERR = 10; - p.INVALID_STATE_ERR = 11; - p.SYNTAX_ERR = 12; - p.INVALID_MODIFICATION_ERR = 13; - p.NAMESPACE_ERR = 14; - p.INVALID_ACCESS_ERR = 15; - p.VALIDATION_ERR = 16; - p.TYPE_MISMATCH_ERR = 17; - - // set up externs - G_vmlCanvasManager = G_vmlCanvasManager_; - CanvasRenderingContext2D = CanvasRenderingContext2D_; - CanvasGradient = CanvasGradient_; - CanvasPattern = CanvasPattern_; - DOMException = DOMException_; -})(); - -} // if diff --git a/frontend/src/vendor/jquery.flot/excanvas.min.js b/frontend/src/vendor/jquery.flot/excanvas.min.js deleted file mode 100644 index 12c74f7bea8..00000000000 --- a/frontend/src/vendor/jquery.flot/excanvas.min.js +++ /dev/null @@ -1 +0,0 @@ -if(!document.createElement("canvas").getContext){(function(){var z=Math;var K=z.round;var J=z.sin;var U=z.cos;var b=z.abs;var k=z.sqrt;var D=10;var F=D/2;function T(){return this.context_||(this.context_=new W(this))}var O=Array.prototype.slice;function G(i,j,m){var Z=O.call(arguments,2);return function(){return i.apply(j,Z.concat(O.call(arguments)))}}function AD(Z){return String(Z).replace(/&/g,"&").replace(/"/g,""")}function r(i){if(!i.namespaces.g_vml_){i.namespaces.add("g_vml_","urn:schemas-microsoft-com:vml","#default#VML")}if(!i.namespaces.g_o_){i.namespaces.add("g_o_","urn:schemas-microsoft-com:office:office","#default#VML")}if(!i.styleSheets.ex_canvas_){var Z=i.createStyleSheet();Z.owningElement.id="ex_canvas_";Z.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}"}}r(document);var E={init:function(Z){if(/MSIE/.test(navigator.userAgent)&&!window.opera){var i=Z||document;i.createElement("canvas");i.attachEvent("onreadystatechange",G(this.init_,this,i))}},init_:function(m){var j=m.getElementsByTagName("canvas");for(var Z=0;Z1){j--}if(6*j<1){return i+(Z-i)*6*j}else{if(2*j<1){return Z}else{if(3*j<2){return i+(Z-i)*(2/3-j)*6}else{return i}}}}function Y(Z){var AE,p=1;Z=String(Z);if(Z.charAt(0)=="#"){AE=Z}else{if(/^rgb/.test(Z)){var m=g(Z);var AE="#",AF;for(var j=0;j<3;j++){if(m[j].indexOf("%")!=-1){AF=Math.floor(C(m[j])*255)}else{AF=Number(m[j])}AE+=I[N(AF,0,255)]}p=m[3]}else{if(/^hsl/.test(Z)){var m=g(Z);AE=c(m);p=m[3]}else{AE=B[Z]||Z}}}return{color:AE,alpha:p}}var L={style:"normal",variant:"normal",weight:"normal",size:10,family:"sans-serif"};var f={};function X(Z){if(f[Z]){return f[Z]}var m=document.createElement("div");var j=m.style;try{j.font=Z}catch(i){}return f[Z]={style:j.fontStyle||L.style,variant:j.fontVariant||L.variant,weight:j.fontWeight||L.weight,size:j.fontSize||L.size,family:j.fontFamily||L.family}}function P(j,i){var Z={};for(var AF in j){Z[AF]=j[AF]}var AE=parseFloat(i.currentStyle.fontSize),m=parseFloat(j.size);if(typeof j.size=="number"){Z.size=j.size}else{if(j.size.indexOf("px")!=-1){Z.size=m}else{if(j.size.indexOf("em")!=-1){Z.size=AE*m}else{if(j.size.indexOf("%")!=-1){Z.size=(AE/100)*m}else{if(j.size.indexOf("pt")!=-1){Z.size=m/0.75}else{Z.size=AE}}}}}Z.size*=0.981;return Z}function AA(Z){return Z.style+" "+Z.variant+" "+Z.weight+" "+Z.size+"px "+Z.family}function t(Z){switch(Z){case"butt":return"flat";case"round":return"round";case"square":default:return"square"}}function W(i){this.m_=V();this.mStack_=[];this.aStack_=[];this.currentPath_=[];this.strokeStyle="#000";this.fillStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=D*1;this.globalAlpha=1;this.font="10px sans-serif";this.textAlign="left";this.textBaseline="alphabetic";this.canvas=i;var Z=i.ownerDocument.createElement("div");Z.style.width=i.clientWidth+"px";Z.style.height=i.clientHeight+"px";Z.style.overflow="hidden";Z.style.position="absolute";i.appendChild(Z);this.element_=Z;this.arcScaleX_=1;this.arcScaleY_=1;this.lineScale_=1}var M=W.prototype;M.clearRect=function(){if(this.textMeasureEl_){this.textMeasureEl_.removeNode(true);this.textMeasureEl_=null}this.element_.innerHTML=""};M.beginPath=function(){this.currentPath_=[]};M.moveTo=function(i,Z){var j=this.getCoords_(i,Z);this.currentPath_.push({type:"moveTo",x:j.x,y:j.y});this.currentX_=j.x;this.currentY_=j.y};M.lineTo=function(i,Z){var j=this.getCoords_(i,Z);this.currentPath_.push({type:"lineTo",x:j.x,y:j.y});this.currentX_=j.x;this.currentY_=j.y};M.bezierCurveTo=function(j,i,AI,AH,AG,AE){var Z=this.getCoords_(AG,AE);var AF=this.getCoords_(j,i);var m=this.getCoords_(AI,AH);e(this,AF,m,Z)};function e(Z,m,j,i){Z.currentPath_.push({type:"bezierCurveTo",cp1x:m.x,cp1y:m.y,cp2x:j.x,cp2y:j.y,x:i.x,y:i.y});Z.currentX_=i.x;Z.currentY_=i.y}M.quadraticCurveTo=function(AG,j,i,Z){var AF=this.getCoords_(AG,j);var AE=this.getCoords_(i,Z);var AH={x:this.currentX_+2/3*(AF.x-this.currentX_),y:this.currentY_+2/3*(AF.y-this.currentY_)};var m={x:AH.x+(AE.x-this.currentX_)/3,y:AH.y+(AE.y-this.currentY_)/3};e(this,AH,m,AE)};M.arc=function(AJ,AH,AI,AE,i,j){AI*=D;var AN=j?"at":"wa";var AK=AJ+U(AE)*AI-F;var AM=AH+J(AE)*AI-F;var Z=AJ+U(i)*AI-F;var AL=AH+J(i)*AI-F;if(AK==Z&&!j){AK+=0.125}var m=this.getCoords_(AJ,AH);var AG=this.getCoords_(AK,AM);var AF=this.getCoords_(Z,AL);this.currentPath_.push({type:AN,x:m.x,y:m.y,radius:AI,xStart:AG.x,yStart:AG.y,xEnd:AF.x,yEnd:AF.y})};M.rect=function(j,i,Z,m){this.moveTo(j,i);this.lineTo(j+Z,i);this.lineTo(j+Z,i+m);this.lineTo(j,i+m);this.closePath()};M.strokeRect=function(j,i,Z,m){var p=this.currentPath_;this.beginPath();this.moveTo(j,i);this.lineTo(j+Z,i);this.lineTo(j+Z,i+m);this.lineTo(j,i+m);this.closePath();this.stroke();this.currentPath_=p};M.fillRect=function(j,i,Z,m){var p=this.currentPath_;this.beginPath();this.moveTo(j,i);this.lineTo(j+Z,i);this.lineTo(j+Z,i+m);this.lineTo(j,i+m);this.closePath();this.fill();this.currentPath_=p};M.createLinearGradient=function(i,m,Z,j){var p=new v("gradient");p.x0_=i;p.y0_=m;p.x1_=Z;p.y1_=j;return p};M.createRadialGradient=function(m,AE,j,i,p,Z){var AF=new v("gradientradial");AF.x0_=m;AF.y0_=AE;AF.r0_=j;AF.x1_=i;AF.y1_=p;AF.r1_=Z;return AF};M.drawImage=function(AO,j){var AH,AF,AJ,AV,AM,AK,AQ,AX;var AI=AO.runtimeStyle.width;var AN=AO.runtimeStyle.height;AO.runtimeStyle.width="auto";AO.runtimeStyle.height="auto";var AG=AO.width;var AT=AO.height;AO.runtimeStyle.width=AI;AO.runtimeStyle.height=AN;if(arguments.length==3){AH=arguments[1];AF=arguments[2];AM=AK=0;AQ=AJ=AG;AX=AV=AT}else{if(arguments.length==5){AH=arguments[1];AF=arguments[2];AJ=arguments[3];AV=arguments[4];AM=AK=0;AQ=AG;AX=AT}else{if(arguments.length==9){AM=arguments[1];AK=arguments[2];AQ=arguments[3];AX=arguments[4];AH=arguments[5];AF=arguments[6];AJ=arguments[7];AV=arguments[8]}else{throw Error("Invalid number of arguments")}}}var AW=this.getCoords_(AH,AF);var m=AQ/2;var i=AX/2;var AU=[];var Z=10;var AE=10;AU.push(" ','","");this.element_.insertAdjacentHTML("BeforeEnd",AU.join(""))};M.stroke=function(AM){var m=10;var AN=10;var AE=5000;var AG={x:null,y:null};var AL={x:null,y:null};for(var AH=0;AHAL.x){AL.x=Z.x}if(AG.y==null||Z.yAL.y){AL.y=Z.y}}}AK.push(' ">');if(!AM){R(this,AK)}else{a(this,AK,AG,AL)}AK.push("");this.element_.insertAdjacentHTML("beforeEnd",AK.join(""))}};function R(j,AE){var i=Y(j.strokeStyle);var m=i.color;var p=i.alpha*j.globalAlpha;var Z=j.lineScale_*j.lineWidth;if(Z<1){p*=Z}AE.push("')}function a(AO,AG,Ah,AP){var AH=AO.fillStyle;var AY=AO.arcScaleX_;var AX=AO.arcScaleY_;var Z=AP.x-Ah.x;var m=AP.y-Ah.y;if(AH instanceof v){var AL=0;var Ac={x:0,y:0};var AU=0;var AK=1;if(AH.type_=="gradient"){var AJ=AH.x0_/AY;var j=AH.y0_/AX;var AI=AH.x1_/AY;var Aj=AH.y1_/AX;var Ag=AO.getCoords_(AJ,j);var Af=AO.getCoords_(AI,Aj);var AE=Af.x-Ag.x;var p=Af.y-Ag.y;AL=Math.atan2(AE,p)*180/Math.PI;if(AL<0){AL+=360}if(AL<0.000001){AL=0}}else{var Ag=AO.getCoords_(AH.x0_,AH.y0_);Ac={x:(Ag.x-Ah.x)/Z,y:(Ag.y-Ah.y)/m};Z/=AY*D;m/=AX*D;var Aa=z.max(Z,m);AU=2*AH.r0_/Aa;AK=2*AH.r1_/Aa-AU}var AS=AH.colors_;AS.sort(function(Ak,i){return Ak.offset-i.offset});var AN=AS.length;var AR=AS[0].color;var AQ=AS[AN-1].color;var AW=AS[0].alpha*AO.globalAlpha;var AV=AS[AN-1].alpha*AO.globalAlpha;var Ab=[];for(var Ae=0;Ae')}else{if(AH instanceof u){if(Z&&m){var AF=-Ah.x;var AZ=-Ah.y;AG.push("')}}else{var Ai=Y(AO.fillStyle);var AT=Ai.color;var Ad=Ai.alpha*AO.globalAlpha;AG.push('')}}}M.fill=function(){this.stroke(true)};M.closePath=function(){this.currentPath_.push({type:"close"})};M.getCoords_=function(j,i){var Z=this.m_;return{x:D*(j*Z[0][0]+i*Z[1][0]+Z[2][0])-F,y:D*(j*Z[0][1]+i*Z[1][1]+Z[2][1])-F}};M.save=function(){var Z={};Q(this,Z);this.aStack_.push(Z);this.mStack_.push(this.m_);this.m_=d(V(),this.m_)};M.restore=function(){if(this.aStack_.length){Q(this.aStack_.pop(),this);this.m_=this.mStack_.pop()}};function H(Z){return isFinite(Z[0][0])&&isFinite(Z[0][1])&&isFinite(Z[1][0])&&isFinite(Z[1][1])&&isFinite(Z[2][0])&&isFinite(Z[2][1])}function y(i,Z,j){if(!H(Z)){return }i.m_=Z;if(j){var p=Z[0][0]*Z[1][1]-Z[0][1]*Z[1][0];i.lineScale_=k(b(p))}}M.translate=function(j,i){var Z=[[1,0,0],[0,1,0],[j,i,1]];y(this,d(Z,this.m_),false)};M.rotate=function(i){var m=U(i);var j=J(i);var Z=[[m,j,0],[-j,m,0],[0,0,1]];y(this,d(Z,this.m_),false)};M.scale=function(j,i){this.arcScaleX_*=j;this.arcScaleY_*=i;var Z=[[j,0,0],[0,i,0],[0,0,1]];y(this,d(Z,this.m_),true)};M.transform=function(p,m,AF,AE,i,Z){var j=[[p,m,0],[AF,AE,0],[i,Z,1]];y(this,d(j,this.m_),true)};M.setTransform=function(AE,p,AG,AF,j,i){var Z=[[AE,p,0],[AG,AF,0],[j,i,1]];y(this,Z,true)};M.drawText_=function(AK,AI,AH,AN,AG){var AM=this.m_,AQ=1000,i=0,AP=AQ,AF={x:0,y:0},AE=[];var Z=P(X(this.font),this.element_);var j=AA(Z);var AR=this.element_.currentStyle;var p=this.textAlign.toLowerCase();switch(p){case"left":case"center":case"right":break;case"end":p=AR.direction=="ltr"?"right":"left";break;case"start":p=AR.direction=="rtl"?"right":"left";break;default:p="left"}switch(this.textBaseline){case"hanging":case"top":AF.y=Z.size/1.75;break;case"middle":break;default:case null:case"alphabetic":case"ideographic":case"bottom":AF.y=-Z.size/2.25;break}switch(p){case"right":i=AQ;AP=0.05;break;case"center":i=AP=AQ/2;break}var AO=this.getCoords_(AI+AF.x,AH+AF.y);AE.push('');if(AG){R(this,AE)}else{a(this,AE,{x:-i,y:0},{x:AP,y:Z.size})}var AL=AM[0][0].toFixed(3)+","+AM[1][0].toFixed(3)+","+AM[0][1].toFixed(3)+","+AM[1][1].toFixed(3)+",0,0";var AJ=K(AO.x/D)+","+K(AO.y/D);AE.push('','','');this.element_.insertAdjacentHTML("beforeEnd",AE.join(""))};M.fillText=function(j,Z,m,i){this.drawText_(j,Z,m,i,false)};M.strokeText=function(j,Z,m,i){this.drawText_(j,Z,m,i,true)};M.measureText=function(j){if(!this.textMeasureEl_){var Z='';this.element_.insertAdjacentHTML("beforeEnd",Z);this.textMeasureEl_=this.element_.lastChild}var i=this.element_.ownerDocument;this.textMeasureEl_.innerHTML="";this.textMeasureEl_.style.font=this.font;this.textMeasureEl_.appendChild(i.createTextNode(j));return{width:this.textMeasureEl_.offsetWidth}};M.clip=function(){};M.arcTo=function(){};M.createPattern=function(i,Z){return new u(i,Z)};function v(Z){this.type_=Z;this.x0_=0;this.y0_=0;this.r0_=0;this.x1_=0;this.y1_=0;this.r1_=0;this.colors_=[]}v.prototype.addColorStop=function(i,Z){Z=Y(Z);this.colors_.push({offset:i,color:Z.color,alpha:Z.alpha})};function u(i,Z){q(i);switch(Z){case"repeat":case null:case"":this.repetition_="repeat";break;case"repeat-x":case"repeat-y":case"no-repeat":this.repetition_=Z;break;default:n("SYNTAX_ERR")}this.src_=i.src;this.width_=i.width;this.height_=i.height}function n(Z){throw new o(Z)}function q(Z){if(!Z||Z.nodeType!=1||Z.tagName!="IMG"){n("TYPE_MISMATCH_ERR")}if(Z.readyState!="complete"){n("INVALID_STATE_ERR")}}function o(Z){this.code=this[Z];this.message=Z+": DOM Exception "+this.code}var x=o.prototype=new Error;x.INDEX_SIZE_ERR=1;x.DOMSTRING_SIZE_ERR=2;x.HIERARCHY_REQUEST_ERR=3;x.WRONG_DOCUMENT_ERR=4;x.INVALID_CHARACTER_ERR=5;x.NO_DATA_ALLOWED_ERR=6;x.NO_MODIFICATION_ALLOWED_ERR=7;x.NOT_FOUND_ERR=8;x.NOT_SUPPORTED_ERR=9;x.INUSE_ATTRIBUTE_ERR=10;x.INVALID_STATE_ERR=11;x.SYNTAX_ERR=12;x.INVALID_MODIFICATION_ERR=13;x.NAMESPACE_ERR=14;x.INVALID_ACCESS_ERR=15;x.VALIDATION_ERR=16;x.TYPE_MISMATCH_ERR=17;G_vmlCanvasManager=E;CanvasRenderingContext2D=W;CanvasGradient=v;CanvasPattern=u;DOMException=o})()}; \ No newline at end of file diff --git a/frontend/src/vendor/jquery.flot/jquery.colorhelpers.js b/frontend/src/vendor/jquery.flot/jquery.colorhelpers.js deleted file mode 100644 index d3524d786f0..00000000000 --- a/frontend/src/vendor/jquery.flot/jquery.colorhelpers.js +++ /dev/null @@ -1,179 +0,0 @@ -/* Plugin for jQuery for working with colors. - * - * Version 1.1. - * - * Inspiration from jQuery color animation plugin by John Resig. - * - * Released under the MIT license by Ole Laursen, October 2009. - * - * Examples: - * - * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString() - * var c = $.color.extract($("#mydiv"), 'background-color'); - * console.log(c.r, c.g, c.b, c.a); - * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)" - * - * Note that .scale() and .add() return the same modified object - * instead of making a new one. - * - * V. 1.1: Fix error handling so e.g. parsing an empty string does - * produce a color rather than just crashing. - */ - -(function($) { - $.color = {}; - - // construct color object with some convenient chainable helpers - $.color.make = function (r, g, b, a) { - var o = {}; - o.r = r || 0; - o.g = g || 0; - o.b = b || 0; - o.a = a != null ? a : 1; - - o.add = function (c, d) { - for (var i = 0; i < c.length; ++i) - o[c.charAt(i)] += d; - return o.normalize(); - }; - - o.scale = function (c, f) { - for (var i = 0; i < c.length; ++i) - o[c.charAt(i)] *= f; - return o.normalize(); - }; - - o.toString = function () { - if (o.a >= 1.0) { - return "rgb("+[o.r, o.g, o.b].join(",")+")"; - } else { - return "rgba("+[o.r, o.g, o.b, o.a].join(",")+")"; - } - }; - - o.normalize = function () { - function clamp(min, value, max) { - return value < min ? min: (value > max ? max: value); - } - - o.r = clamp(0, parseInt(o.r), 255); - o.g = clamp(0, parseInt(o.g), 255); - o.b = clamp(0, parseInt(o.b), 255); - o.a = clamp(0, o.a, 1); - return o; - }; - - o.clone = function () { - return $.color.make(o.r, o.b, o.g, o.a); - }; - - return o.normalize(); - } - - // extract CSS color property from element, going up in the DOM - // if it's "transparent" - $.color.extract = function (elem, css) { - var c; - do { - c = elem.css(css).toLowerCase(); - // keep going until we find an element that has color, or - // we hit the body - if (c != '' && c != 'transparent') - break; - elem = elem.parent(); - } while (!$.nodeName(elem.get(0), "body")); - - // catch Safari's way of signalling transparent - if (c == "rgba(0, 0, 0, 0)") - c = "transparent"; - - return $.color.parse(c); - } - - // parse CSS color string (like "rgb(10, 32, 43)" or "#fff"), - // returns color object, if parsing failed, you get black (0, 0, - // 0) out - $.color.parse = function (str) { - var res, m = $.color.make; - - // Look for rgb(num,num,num) - if (res = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str)) - return m(parseInt(res[1], 10), parseInt(res[2], 10), parseInt(res[3], 10)); - - // Look for rgba(num,num,num,num) - if (res = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str)) - return m(parseInt(res[1], 10), parseInt(res[2], 10), parseInt(res[3], 10), parseFloat(res[4])); - - // Look for rgb(num%,num%,num%) - if (res = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str)) - return m(parseFloat(res[1])*2.55, parseFloat(res[2])*2.55, parseFloat(res[3])*2.55); - - // Look for rgba(num%,num%,num%,num) - if (res = /rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str)) - return m(parseFloat(res[1])*2.55, parseFloat(res[2])*2.55, parseFloat(res[3])*2.55, parseFloat(res[4])); - - // Look for #a0b1c2 - if (res = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str)) - return m(parseInt(res[1], 16), parseInt(res[2], 16), parseInt(res[3], 16)); - - // Look for #fff - if (res = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str)) - return m(parseInt(res[1]+res[1], 16), parseInt(res[2]+res[2], 16), parseInt(res[3]+res[3], 16)); - - // Otherwise, we're most likely dealing with a named color - var name = $.trim(str).toLowerCase(); - if (name == "transparent") - return m(255, 255, 255, 0); - else { - // default to black - res = lookupColors[name] || [0, 0, 0]; - return m(res[0], res[1], res[2]); - } - } - - var lookupColors = { - aqua:[0,255,255], - azure:[240,255,255], - beige:[245,245,220], - black:[0,0,0], - blue:[0,0,255], - brown:[165,42,42], - cyan:[0,255,255], - darkblue:[0,0,139], - darkcyan:[0,139,139], - darkgrey:[169,169,169], - darkgreen:[0,100,0], - darkkhaki:[189,183,107], - darkmagenta:[139,0,139], - darkolivegreen:[85,107,47], - darkorange:[255,140,0], - darkorchid:[153,50,204], - darkred:[139,0,0], - darksalmon:[233,150,122], - darkviolet:[148,0,211], - fuchsia:[255,0,255], - gold:[255,215,0], - green:[0,128,0], - indigo:[75,0,130], - khaki:[240,230,140], - lightblue:[173,216,230], - lightcyan:[224,255,255], - lightgreen:[144,238,144], - lightgrey:[211,211,211], - lightpink:[255,182,193], - lightyellow:[255,255,224], - lime:[0,255,0], - magenta:[255,0,255], - maroon:[128,0,0], - navy:[0,0,128], - olive:[128,128,0], - orange:[255,165,0], - pink:[255,192,203], - purple:[128,0,128], - violet:[128,0,128], - red:[255,0,0], - silver:[192,192,192], - white:[255,255,255], - yellow:[255,255,0] - }; -})(jQuery); diff --git a/frontend/src/vendor/jquery.flot/jquery.colorhelpers.min.js b/frontend/src/vendor/jquery.flot/jquery.colorhelpers.min.js deleted file mode 100644 index 7f44c57b560..00000000000 --- a/frontend/src/vendor/jquery.flot/jquery.colorhelpers.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(b){b.color={};b.color.make=function(f,e,c,d){var h={};h.r=f||0;h.g=e||0;h.b=c||0;h.a=d!=null?d:1;h.add=function(k,j){for(var g=0;g=1){return"rgb("+[h.r,h.g,h.b].join(",")+")"}else{return"rgba("+[h.r,h.g,h.b,h.a].join(",")+")"}};h.normalize=function(){function g(j,k,i){return ki?i:k)}h.r=g(0,parseInt(h.r),255);h.g=g(0,parseInt(h.g),255);h.b=g(0,parseInt(h.b),255);h.a=g(0,h.a,1);return h};h.clone=function(){return b.color.make(h.r,h.b,h.g,h.a)};return h.normalize()};b.color.extract=function(e,d){var f;do{f=e.css(d).toLowerCase();if(f!=""&&f!="transparent"){break}e=e.parent()}while(!b.nodeName(e.get(0),"body"));if(f=="rgba(0, 0, 0, 0)"){f="transparent"}return b.color.parse(f)};b.color.parse=function(f){var e,c=b.color.make;if(e=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(f)){return c(parseInt(e[1],10),parseInt(e[2],10),parseInt(e[3],10))}if(e=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(f)){return c(parseInt(e[1],10),parseInt(e[2],10),parseInt(e[3],10),parseFloat(e[4]))}if(e=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(f)){return c(parseFloat(e[1])*2.55,parseFloat(e[2])*2.55,parseFloat(e[3])*2.55)}if(e=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(f)){return c(parseFloat(e[1])*2.55,parseFloat(e[2])*2.55,parseFloat(e[3])*2.55,parseFloat(e[4]))}if(e=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(f)){return c(parseInt(e[1],16),parseInt(e[2],16),parseInt(e[3],16))}if(e=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(f)){return c(parseInt(e[1]+e[1],16),parseInt(e[2]+e[2],16),parseInt(e[3]+e[3],16))}var d=b.trim(f).toLowerCase();if(d=="transparent"){return c(255,255,255,0)}else{e=a[d]||[0,0,0];return c(e[0],e[1],e[2])}};var a={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery); \ No newline at end of file diff --git a/frontend/src/vendor/jquery.flot/jquery.flot.crosshair.js b/frontend/src/vendor/jquery.flot/jquery.flot.crosshair.js deleted file mode 100644 index 1d433f0074d..00000000000 --- a/frontend/src/vendor/jquery.flot/jquery.flot.crosshair.js +++ /dev/null @@ -1,167 +0,0 @@ -/* -Flot plugin for showing crosshairs, thin lines, when the mouse hovers -over the plot. - - crosshair: { - mode: null or "x" or "y" or "xy" - color: color - lineWidth: number - } - -Set the mode to one of "x", "y" or "xy". The "x" mode enables a -vertical crosshair that lets you trace the values on the x axis, "y" -enables a horizontal crosshair and "xy" enables them both. "color" is -the color of the crosshair (default is "rgba(170, 0, 0, 0.80)"), -"lineWidth" is the width of the drawn lines (default is 1). - -The plugin also adds four public methods: - - - setCrosshair(pos) - - Set the position of the crosshair. Note that this is cleared if - the user moves the mouse. "pos" is in coordinates of the plot and - should be on the form { x: xpos, y: ypos } (you can use x2/x3/... - if you're using multiple axes), which is coincidentally the same - format as what you get from a "plothover" event. If "pos" is null, - the crosshair is cleared. - - - clearCrosshair() - - Clear the crosshair. - - - lockCrosshair(pos) - - Cause the crosshair to lock to the current location, no longer - updating if the user moves the mouse. Optionally supply a position - (passed on to setCrosshair()) to move it to. - - Example usage: - var myFlot = $.plot( $("#graph"), ..., { crosshair: { mode: "x" } } }; - $("#graph").bind("plothover", function (evt, position, item) { - if (item) { - // Lock the crosshair to the data point being hovered - myFlot.lockCrosshair({ x: item.datapoint[0], y: item.datapoint[1] }); - } - else { - // Return normal crosshair operation - myFlot.unlockCrosshair(); - } - }); - - - unlockCrosshair() - - Free the crosshair to move again after locking it. -*/ - -(function ($) { - var options = { - crosshair: { - mode: null, // one of null, "x", "y" or "xy", - color: "rgba(170, 0, 0, 0.80)", - lineWidth: 1 - } - }; - - function init(plot) { - // position of crosshair in pixels - var crosshair = { x: -1, y: -1, locked: false }; - - plot.setCrosshair = function setCrosshair(pos) { - if (!pos) - crosshair.x = -1; - else { - var o = plot.p2c(pos); - crosshair.x = Math.max(0, Math.min(o.left, plot.width())); - crosshair.y = Math.max(0, Math.min(o.top, plot.height())); - } - - plot.triggerRedrawOverlay(); - }; - - plot.clearCrosshair = plot.setCrosshair; // passes null for pos - - plot.lockCrosshair = function lockCrosshair(pos) { - if (pos) - plot.setCrosshair(pos); - crosshair.locked = true; - } - - plot.unlockCrosshair = function unlockCrosshair() { - crosshair.locked = false; - } - - function onMouseOut(e) { - if (crosshair.locked) - return; - - if (crosshair.x != -1) { - crosshair.x = -1; - plot.triggerRedrawOverlay(); - } - } - - function onMouseMove(e) { - if (crosshair.locked) - return; - - if (plot.getSelection && plot.getSelection()) { - crosshair.x = -1; // hide the crosshair while selecting - return; - } - - var offset = plot.offset(); - crosshair.x = Math.max(0, Math.min(e.pageX - offset.left, plot.width())); - crosshair.y = Math.max(0, Math.min(e.pageY - offset.top, plot.height())); - plot.triggerRedrawOverlay(); - } - - plot.hooks.bindEvents.push(function (plot, eventHolder) { - if (!plot.getOptions().crosshair.mode) - return; - - eventHolder.mouseout(onMouseOut); - eventHolder.mousemove(onMouseMove); - }); - - plot.hooks.drawOverlay.push(function (plot, ctx) { - var c = plot.getOptions().crosshair; - if (!c.mode) - return; - - var plotOffset = plot.getPlotOffset(); - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - if (crosshair.x != -1) { - ctx.strokeStyle = c.color; - ctx.lineWidth = c.lineWidth; - ctx.lineJoin = "round"; - - ctx.beginPath(); - if (c.mode.indexOf("x") != -1) { - ctx.moveTo(crosshair.x, 0); - ctx.lineTo(crosshair.x, plot.height()); - } - if (c.mode.indexOf("y") != -1) { - ctx.moveTo(0, crosshair.y); - ctx.lineTo(plot.width(), crosshair.y); - } - ctx.stroke(); - } - ctx.restore(); - }); - - plot.hooks.shutdown.push(function (plot, eventHolder) { - eventHolder.unbind("mouseout", onMouseOut); - eventHolder.unbind("mousemove", onMouseMove); - }); - } - - $.plot.plugins.push({ - init: init, - options: options, - name: 'crosshair', - version: '1.0' - }); -})(jQuery); diff --git a/frontend/src/vendor/jquery.flot/jquery.flot.crosshair.min.js b/frontend/src/vendor/jquery.flot/jquery.flot.crosshair.min.js deleted file mode 100644 index ccaf240366a..00000000000 --- a/frontend/src/vendor/jquery.flot/jquery.flot.crosshair.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(b){var a={crosshair:{mode:null,color:"rgba(170, 0, 0, 0.80)",lineWidth:1}};function c(h){var j={x:-1,y:-1,locked:false};h.setCrosshair=function e(l){if(!l){j.x=-1}else{var k=h.p2c(l);j.x=Math.max(0,Math.min(k.left,h.width()));j.y=Math.max(0,Math.min(k.top,h.height()))}h.triggerRedrawOverlay()};h.clearCrosshair=h.setCrosshair;h.lockCrosshair=function f(k){if(k){h.setCrosshair(k)}j.locked=true};h.unlockCrosshair=function g(){j.locked=false};function d(k){if(j.locked){return}if(j.x!=-1){j.x=-1;h.triggerRedrawOverlay()}}function i(k){if(j.locked){return}if(h.getSelection&&h.getSelection()){j.x=-1;return}var l=h.offset();j.x=Math.max(0,Math.min(k.pageX-l.left,h.width()));j.y=Math.max(0,Math.min(k.pageY-l.top,h.height()));h.triggerRedrawOverlay()}h.hooks.bindEvents.push(function(l,k){if(!l.getOptions().crosshair.mode){return}k.mouseout(d);k.mousemove(i)});h.hooks.drawOverlay.push(function(m,k){var n=m.getOptions().crosshair;if(!n.mode){return}var l=m.getPlotOffset();k.save();k.translate(l.left,l.top);if(j.x!=-1){k.strokeStyle=n.color;k.lineWidth=n.lineWidth;k.lineJoin="round";k.beginPath();if(n.mode.indexOf("x")!=-1){k.moveTo(j.x,0);k.lineTo(j.x,m.height())}if(n.mode.indexOf("y")!=-1){k.moveTo(0,j.y);k.lineTo(m.width(),j.y)}k.stroke()}k.restore()});h.hooks.shutdown.push(function(l,k){k.unbind("mouseout",d);k.unbind("mousemove",i)})}b.plot.plugins.push({init:c,options:a,name:"crosshair",version:"1.0"})})(jQuery); \ No newline at end of file diff --git a/frontend/src/vendor/jquery.flot/jquery.flot.fillbetween.js b/frontend/src/vendor/jquery.flot/jquery.flot.fillbetween.js deleted file mode 100644 index 69700e79ce6..00000000000 --- a/frontend/src/vendor/jquery.flot/jquery.flot.fillbetween.js +++ /dev/null @@ -1,183 +0,0 @@ -/* -Flot plugin for computing bottoms for filled line and bar charts. - -The case: you've got two series that you want to fill the area -between. In Flot terms, you need to use one as the fill bottom of the -other. You can specify the bottom of each data point as the third -coordinate manually, or you can use this plugin to compute it for you. - -In order to name the other series, you need to give it an id, like this - - var dataset = [ - { data: [ ... ], id: "foo" } , // use default bottom - { data: [ ... ], fillBetween: "foo" }, // use first dataset as bottom - ]; - - $.plot($("#placeholder"), dataset, { line: { show: true, fill: true }}); - -As a convenience, if the id given is a number that doesn't appear as -an id in the series, it is interpreted as the index in the array -instead (so fillBetween: 0 can also mean the first series). - -Internally, the plugin modifies the datapoints in each series. For -line series, extra data points might be inserted through -interpolation. Note that at points where the bottom line is not -defined (due to a null point or start/end of line), the current line -will show a gap too. The algorithm comes from the jquery.flot.stack.js -plugin, possibly some code could be shared. -*/ - -(function ($) { - var options = { - series: { fillBetween: null } // or number - }; - - function init(plot) { - function findBottomSeries(s, allseries) { - var i; - for (i = 0; i < allseries.length; ++i) { - if (allseries[i].id == s.fillBetween) - return allseries[i]; - } - - if (typeof s.fillBetween == "number") { - i = s.fillBetween; - - if (i < 0 || i >= allseries.length) - return null; - - return allseries[i]; - } - - return null; - } - - function computeFillBottoms(plot, s, datapoints) { - if (s.fillBetween == null) - return; - - var other = findBottomSeries(s, plot.getData()); - if (!other) - return; - - var ps = datapoints.pointsize, - points = datapoints.points, - otherps = other.datapoints.pointsize, - otherpoints = other.datapoints.points, - newpoints = [], - px, py, intery, qx, qy, bottom, - withlines = s.lines.show, - withbottom = ps > 2 && datapoints.format[2].y, - withsteps = withlines && s.lines.steps, - fromgap = true, - i = 0, j = 0, l; - - while (true) { - if (i >= points.length) - break; - - l = newpoints.length; - - if (points[i] == null) { - // copy gaps - for (m = 0; m < ps; ++m) - newpoints.push(points[i + m]); - i += ps; - } - else if (j >= otherpoints.length) { - // for lines, we can't use the rest of the points - if (!withlines) { - for (m = 0; m < ps; ++m) - newpoints.push(points[i + m]); - } - i += ps; - } - else if (otherpoints[j] == null) { - // oops, got a gap - for (m = 0; m < ps; ++m) - newpoints.push(null); - fromgap = true; - j += otherps; - } - else { - // cases where we actually got two points - px = points[i]; - py = points[i + 1]; - qx = otherpoints[j]; - qy = otherpoints[j + 1]; - bottom = 0; - - if (px == qx) { - for (m = 0; m < ps; ++m) - newpoints.push(points[i + m]); - - //newpoints[l + 1] += qy; - bottom = qy; - - i += ps; - j += otherps; - } - else if (px > qx) { - // we got past point below, might need to - // insert interpolated extra point - if (withlines && i > 0 && points[i - ps] != null) { - intery = py + (points[i - ps + 1] - py) * (qx - px) / (points[i - ps] - px); - newpoints.push(qx); - newpoints.push(intery) - for (m = 2; m < ps; ++m) - newpoints.push(points[i + m]); - bottom = qy; - } - - j += otherps; - } - else { // px < qx - if (fromgap && withlines) { - // if we come from a gap, we just skip this point - i += ps; - continue; - } - - for (m = 0; m < ps; ++m) - newpoints.push(points[i + m]); - - // we might be able to interpolate a point below, - // this can give us a better y - if (withlines && j > 0 && otherpoints[j - otherps] != null) - bottom = qy + (otherpoints[j - otherps + 1] - qy) * (px - qx) / (otherpoints[j - otherps] - qx); - - //newpoints[l + 1] += bottom; - - i += ps; - } - - fromgap = false; - - if (l != newpoints.length && withbottom) - newpoints[l + 2] = bottom; - } - - // maintain the line steps invariant - if (withsteps && l != newpoints.length && l > 0 - && newpoints[l] != null - && newpoints[l] != newpoints[l - ps] - && newpoints[l + 1] != newpoints[l - ps + 1]) { - for (m = 0; m < ps; ++m) - newpoints[l + ps + m] = newpoints[l + m]; - newpoints[l + 1] = newpoints[l - ps + 1]; - } - } - - datapoints.points = newpoints; - } - - plot.hooks.processDatapoints.push(computeFillBottoms); - } - - $.plot.plugins.push({ - init: init, - options: options, - name: 'fillbetween', - version: '1.0' - }); -})(jQuery); diff --git a/frontend/src/vendor/jquery.flot/jquery.flot.fillbetween.min.js b/frontend/src/vendor/jquery.flot/jquery.flot.fillbetween.min.js deleted file mode 100644 index 47f3dfb6de0..00000000000 --- a/frontend/src/vendor/jquery.flot/jquery.flot.fillbetween.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(b){var a={series:{fillBetween:null}};function c(f){function d(j,h){var g;for(g=0;g=h.length){return null}return h[g]}return null}function e(B,u,g){if(u.fillBetween==null){return}var p=d(u,B.getData());if(!p){return}var y=g.pointsize,E=g.points,h=p.datapoints.pointsize,x=p.datapoints.points,r=[],w,v,k,G,F,q,t=u.lines.show,o=y>2&&g.format[2].y,n=t&&u.lines.steps,D=true,C=0,A=0,z;while(true){if(C>=E.length){break}z=r.length;if(E[C]==null){for(m=0;m=x.length){if(!t){for(m=0;mG){if(t&&C>0&&E[C-y]!=null){k=v+(E[C-y+1]-v)*(G-w)/(E[C-y]-w);r.push(G);r.push(k);for(m=2;m0&&x[A-h]!=null){q=F+(x[A-h+1]-F)*(w-G)/(x[A-h]-G)}C+=y}}D=false;if(z!=r.length&&o){r[z+2]=q}}}}if(n&&z!=r.length&&z>0&&r[z]!=null&&r[z]!=r[z-y]&&r[z+1]!=r[z-y+1]){for(m=0;m').load(handler).error(handler).attr('src', url); - }); - } - - function drawSeries(plot, ctx, series) { - var plotOffset = plot.getPlotOffset(); - - if (!series.images || !series.images.show) - return; - - var points = series.datapoints.points, - ps = series.datapoints.pointsize; - - for (var i = 0; i < points.length; i += ps) { - var img = points[i], - x1 = points[i + 1], y1 = points[i + 2], - x2 = points[i + 3], y2 = points[i + 4], - xaxis = series.xaxis, yaxis = series.yaxis, - tmp; - - // actually we should check img.complete, but it - // appears to be a somewhat unreliable indicator in - // IE6 (false even after load event) - if (!img || img.width <= 0 || img.height <= 0) - continue; - - if (x1 > x2) { - tmp = x2; - x2 = x1; - x1 = tmp; - } - if (y1 > y2) { - tmp = y2; - y2 = y1; - y1 = tmp; - } - - // if the anchor is at the center of the pixel, expand the - // image by 1/2 pixel in each direction - if (series.images.anchor == "center") { - tmp = 0.5 * (x2-x1) / (img.width - 1); - x1 -= tmp; - x2 += tmp; - tmp = 0.5 * (y2-y1) / (img.height - 1); - y1 -= tmp; - y2 += tmp; - } - - // clip - if (x1 == x2 || y1 == y2 || - x1 >= xaxis.max || x2 <= xaxis.min || - y1 >= yaxis.max || y2 <= yaxis.min) - continue; - - var sx1 = 0, sy1 = 0, sx2 = img.width, sy2 = img.height; - if (x1 < xaxis.min) { - sx1 += (sx2 - sx1) * (xaxis.min - x1) / (x2 - x1); - x1 = xaxis.min; - } - - if (x2 > xaxis.max) { - sx2 += (sx2 - sx1) * (xaxis.max - x2) / (x2 - x1); - x2 = xaxis.max; - } - - if (y1 < yaxis.min) { - sy2 += (sy1 - sy2) * (yaxis.min - y1) / (y2 - y1); - y1 = yaxis.min; - } - - if (y2 > yaxis.max) { - sy1 += (sy1 - sy2) * (yaxis.max - y2) / (y2 - y1); - y2 = yaxis.max; - } - - x1 = xaxis.p2c(x1); - x2 = xaxis.p2c(x2); - y1 = yaxis.p2c(y1); - y2 = yaxis.p2c(y2); - - // the transformation may have swapped us - if (x1 > x2) { - tmp = x2; - x2 = x1; - x1 = tmp; - } - if (y1 > y2) { - tmp = y2; - y2 = y1; - y1 = tmp; - } - - tmp = ctx.globalAlpha; - ctx.globalAlpha *= series.images.alpha; - ctx.drawImage(img, - sx1, sy1, sx2 - sx1, sy2 - sy1, - x1 + plotOffset.left, y1 + plotOffset.top, - x2 - x1, y2 - y1); - ctx.globalAlpha = tmp; - } - } - - function processRawData(plot, series, data, datapoints) { - if (!series.images.show) - return; - - // format is Image, x1, y1, x2, y2 (opposite corners) - datapoints.format = [ - { required: true }, - { x: true, number: true, required: true }, - { y: true, number: true, required: true }, - { x: true, number: true, required: true }, - { y: true, number: true, required: true } - ]; - } - - function init(plot) { - plot.hooks.processRawData.push(processRawData); - plot.hooks.drawSeries.push(drawSeries); - } - - $.plot.plugins.push({ - init: init, - options: options, - name: 'image', - version: '1.1' - }); -})(jQuery); diff --git a/frontend/src/vendor/jquery.flot/jquery.flot.image.min.js b/frontend/src/vendor/jquery.flot/jquery.flot.image.min.js deleted file mode 100644 index 9480c1e7a31..00000000000 --- a/frontend/src/vendor/jquery.flot/jquery.flot.image.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(c){var a={series:{images:{show:false,alpha:1,anchor:"corner"}}};c.plot.image={};c.plot.image.loadDataImages=function(g,f,k){var j=[],h=[];var i=f.series.images.show;c.each(g,function(l,m){if(!(i||m.images.show)){return}if(m.data){m=m.data}c.each(m,function(n,o){if(typeof o[0]=="string"){j.push(o[0]);h.push(o)}})});c.plot.image.load(j,function(l){c.each(h,function(n,o){var m=o[0];if(l[m]){o[0]=l[m]}});k()})};c.plot.image.load=function(h,i){var g=h.length,f={};if(g==0){i({})}c.each(h,function(k,j){var l=function(){--g;f[j]=this;if(g==0){i(f)}};c("").load(l).error(l).attr("src",j)})};function d(q,o,l){var m=q.getPlotOffset();if(!l.images||!l.images.show){return}var r=l.datapoints.points,n=l.datapoints.pointsize;for(var t=0;tv){x=v;v=w;w=x}if(g>f){x=f;f=g;g=x}if(l.images.anchor=="center"){x=0.5*(v-w)/(y.width-1);w-=x;v+=x;x=0.5*(f-g)/(y.height-1);g-=x;f+=x}if(w==v||g==f||w>=h.max||v<=h.min||g>=u.max||f<=u.min){continue}var k=0,s=0,j=y.width,p=y.height;if(wh.max){j+=(j-k)*(h.max-v)/(v-w);v=h.max}if(gu.max){s+=(s-p)*(u.max-f)/(f-g);f=u.max}w=h.p2c(w);v=h.p2c(v);g=u.p2c(g);f=u.p2c(f);if(w>v){x=v;v=w;w=x}if(g>f){x=f;f=g;g=x}x=o.globalAlpha;o.globalAlpha*=l.images.alpha;o.drawImage(y,k,s,j-k,p-s,w+m.left,g+m.top,v-w,f-g);o.globalAlpha=x}}function b(i,f,g,h){if(!f.images.show){return}h.format=[{required:true},{x:true,number:true,required:true},{y:true,number:true,required:true},{x:true,number:true,required:true},{y:true,number:true,required:true}]}function e(f){f.hooks.processRawData.push(b);f.hooks.drawSeries.push(d)}c.plot.plugins.push({init:e,options:a,name:"image",version:"1.1"})})(jQuery); \ No newline at end of file diff --git a/frontend/src/vendor/jquery.flot/jquery.flot.js b/frontend/src/vendor/jquery.flot/jquery.flot.js deleted file mode 100644 index 28abf7f5c8a..00000000000 --- a/frontend/src/vendor/jquery.flot/jquery.flot.js +++ /dev/null @@ -1,2599 +0,0 @@ -/*! Javascript plotting library for jQuery, v. 0.7. - * - * Released under the MIT license by IOLA, December 2007. - * - */ - -// first an inline dependency, jquery.colorhelpers.js, we inline it here -// for convenience - -/* Plugin for jQuery for working with colors. - * - * Version 1.1. - * - * Inspiration from jQuery color animation plugin by John Resig. - * - * Released under the MIT license by Ole Laursen, October 2009. - * - * Examples: - * - * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString() - * var c = $.color.extract($("#mydiv"), 'background-color'); - * console.log(c.r, c.g, c.b, c.a); - * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)" - * - * Note that .scale() and .add() return the same modified object - * instead of making a new one. - * - * V. 1.1: Fix error handling so e.g. parsing an empty string does - * produce a color rather than just crashing. - */ -(function(B){B.color={};B.color.make=function(F,E,C,D){var G={};G.r=F||0;G.g=E||0;G.b=C||0;G.a=D!=null?D:1;G.add=function(J,I){for(var H=0;H=1){return"rgb("+[G.r,G.g,G.b].join(",")+")"}else{return"rgba("+[G.r,G.g,G.b,G.a].join(",")+")"}};G.normalize=function(){function H(J,K,I){return KI?I:K)}G.r=H(0,parseInt(G.r),255);G.g=H(0,parseInt(G.g),255);G.b=H(0,parseInt(G.b),255);G.a=H(0,G.a,1);return G};G.clone=function(){return B.color.make(G.r,G.b,G.g,G.a)};return G.normalize()};B.color.extract=function(D,C){var E;do{E=D.css(C).toLowerCase();if(E!=""&&E!="transparent"){break}D=D.parent()}while(!B.nodeName(D.get(0),"body"));if(E=="rgba(0, 0, 0, 0)"){E="transparent"}return B.color.parse(E)};B.color.parse=function(F){var E,C=B.color.make;if(E=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10))}if(E=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10),parseFloat(E[4]))}if(E=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55)}if(E=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55,parseFloat(E[4]))}if(E=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(F)){return C(parseInt(E[1],16),parseInt(E[2],16),parseInt(E[3],16))}if(E=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(F)){return C(parseInt(E[1]+E[1],16),parseInt(E[2]+E[2],16),parseInt(E[3]+E[3],16))}var D=B.trim(F).toLowerCase();if(D=="transparent"){return C(255,255,255,0)}else{E=A[D]||[0,0,0];return C(E[0],E[1],E[2])}};var A={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery); - -// the actual Flot code -(function($) { - function Plot(placeholder, data_, options_, plugins) { - // data is on the form: - // [ series1, series2 ... ] - // where series is either just the data as [ [x1, y1], [x2, y2], ... ] - // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... } - - var series = [], - options = { - // the color theme used for graphs - colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"], - legend: { - show: true, - noColumns: 1, // number of colums in legend table - labelFormatter: null, // fn: string -> string - labelBoxBorderColor: "#ccc", // border color for the little label boxes - container: null, // container (as jQuery object) to put legend in, null means default on top of graph - position: "ne", // position of default legend container within plot - margin: 5, // distance from grid edge to default legend container within plot - backgroundColor: null, // null means auto-detect - backgroundOpacity: 0.85 // set to 0 to avoid background - }, - xaxis: { - show: null, // null = auto-detect, true = always, false = never - position: "bottom", // or "top" - mode: null, // null or "time" - color: null, // base color, labels, ticks - tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)" - transform: null, // null or f: number -> number to transform axis - inverseTransform: null, // if transform is set, this should be the inverse function - min: null, // min. value to show, null means set automatically - max: null, // max. value to show, null means set automatically - autoscaleMargin: null, // margin in % to add if auto-setting min/max - ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks - tickFormatter: null, // fn: number -> string - labelWidth: null, // size of tick labels in pixels - labelHeight: null, - reserveSpace: null, // whether to reserve space even if axis isn't shown - tickLength: null, // size in pixels of ticks, or "full" for whole line - alignTicksWithAxis: null, // axis number or null for no sync - - // mode specific options - tickDecimals: null, // no. of decimals, null means auto - tickSize: null, // number or [number, "unit"] - minTickSize: null, // number or [number, "unit"] - monthNames: null, // list of names of months - timeformat: null, // format string to use - twelveHourClock: false // 12 or 24 time in time mode - }, - yaxis: { - autoscaleMargin: 0.02, - position: "left" // or "right" - }, - xaxes: [], - yaxes: [], - series: { - points: { - show: false, - radius: 3, - lineWidth: 2, // in pixels - fill: true, - fillColor: "#ffffff", - symbol: "circle" // or callback - }, - lines: { - // we don't put in show: false so we can see - // whether lines were actively disabled - lineWidth: 2, // in pixels - fill: false, - fillColor: null, - steps: false - }, - bars: { - show: false, - lineWidth: 2, // in pixels - barWidth: 1, // in units of the x axis - fill: true, - fillColor: null, - align: "left", // or "center" - horizontal: false - }, - shadowSize: 3 - }, - grid: { - show: true, - aboveData: false, - color: "#545454", // primary color used for outline and labels - backgroundColor: null, // null for transparent, else color - borderColor: null, // set if different from the grid color - tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)" - labelMargin: 5, // in pixels - axisMargin: 8, // in pixels - borderWidth: 2, // in pixels - minBorderMargin: null, // in pixels, null means taken from points radius - markings: null, // array of ranges or fn: axes -> array of ranges - markingsColor: "#f4f4f4", - markingsLineWidth: 2, - // interactive stuff - clickable: false, - hoverable: false, - autoHighlight: true, // highlight in case mouse is near - mouseActiveRadius: 10 // how far the mouse can be away to activate an item - }, - hooks: {} - }, - canvas = null, // the canvas for the plot itself - overlay = null, // canvas for interactive stuff on top of plot - eventHolder = null, // jQuery object that events should be bound to - ctx = null, octx = null, - xaxes = [], yaxes = [], - plotOffset = { left: 0, right: 0, top: 0, bottom: 0}, - canvasWidth = 0, canvasHeight = 0, - plotWidth = 0, plotHeight = 0, - hooks = { - processOptions: [], - processRawData: [], - processDatapoints: [], - drawSeries: [], - draw: [], - bindEvents: [], - drawOverlay: [], - shutdown: [] - }, - plot = this; - - // public functions - plot.setData = setData; - plot.setupGrid = setupGrid; - plot.draw = draw; - plot.getPlaceholder = function() { return placeholder; }; - plot.getCanvas = function() { return canvas; }; - plot.getPlotOffset = function() { return plotOffset; }; - plot.width = function () { return plotWidth; }; - plot.height = function () { return plotHeight; }; - plot.offset = function () { - var o = eventHolder.offset(); - o.left += plotOffset.left; - o.top += plotOffset.top; - return o; - }; - plot.getData = function () { return series; }; - plot.getAxes = function () { - var res = {}, i; - $.each(xaxes.concat(yaxes), function (_, axis) { - if (axis) - res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis; - }); - return res; - }; - plot.getXAxes = function () { return xaxes; }; - plot.getYAxes = function () { return yaxes; }; - plot.c2p = canvasToAxisCoords; - plot.p2c = axisToCanvasCoords; - plot.getOptions = function () { return options; }; - plot.highlight = highlight; - plot.unhighlight = unhighlight; - plot.triggerRedrawOverlay = triggerRedrawOverlay; - plot.pointOffset = function(point) { - return { - left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left), - top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top) - }; - }; - plot.shutdown = shutdown; - plot.resize = function () { - getCanvasDimensions(); - resizeCanvas(canvas); - resizeCanvas(overlay); - }; - - // public attributes - plot.hooks = hooks; - - // initialize - initPlugins(plot); - parseOptions(options_); - setupCanvases(); - setData(data_); - setupGrid(); - draw(); - bindEvents(); - - - function executeHooks(hook, args) { - args = [plot].concat(args); - for (var i = 0; i < hook.length; ++i) - hook[i].apply(this, args); - } - - function initPlugins() { - for (var i = 0; i < plugins.length; ++i) { - var p = plugins[i]; - p.init(plot); - if (p.options) - $.extend(true, options, p.options); - } - } - - function parseOptions(opts) { - var i; - - $.extend(true, options, opts); - - if (options.xaxis.color == null) - options.xaxis.color = options.grid.color; - if (options.yaxis.color == null) - options.yaxis.color = options.grid.color; - - if (options.xaxis.tickColor == null) // backwards-compatibility - options.xaxis.tickColor = options.grid.tickColor; - if (options.yaxis.tickColor == null) // backwards-compatibility - options.yaxis.tickColor = options.grid.tickColor; - - if (options.grid.borderColor == null) - options.grid.borderColor = options.grid.color; - if (options.grid.tickColor == null) - options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString(); - - // fill in defaults in axes, copy at least always the - // first as the rest of the code assumes it'll be there - for (i = 0; i < Math.max(1, options.xaxes.length); ++i) - options.xaxes[i] = $.extend(true, {}, options.xaxis, options.xaxes[i]); - for (i = 0; i < Math.max(1, options.yaxes.length); ++i) - options.yaxes[i] = $.extend(true, {}, options.yaxis, options.yaxes[i]); - - // backwards compatibility, to be removed in future - if (options.xaxis.noTicks && options.xaxis.ticks == null) - options.xaxis.ticks = options.xaxis.noTicks; - if (options.yaxis.noTicks && options.yaxis.ticks == null) - options.yaxis.ticks = options.yaxis.noTicks; - if (options.x2axis) { - options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis); - options.xaxes[1].position = "top"; - } - if (options.y2axis) { - options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis); - options.yaxes[1].position = "right"; - } - if (options.grid.coloredAreas) - options.grid.markings = options.grid.coloredAreas; - if (options.grid.coloredAreasColor) - options.grid.markingsColor = options.grid.coloredAreasColor; - if (options.lines) - $.extend(true, options.series.lines, options.lines); - if (options.points) - $.extend(true, options.series.points, options.points); - if (options.bars) - $.extend(true, options.series.bars, options.bars); - if (options.shadowSize != null) - options.series.shadowSize = options.shadowSize; - - // save options on axes for future reference - for (i = 0; i < options.xaxes.length; ++i) - getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i]; - for (i = 0; i < options.yaxes.length; ++i) - getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i]; - - // add hooks from options - for (var n in hooks) - if (options.hooks[n] && options.hooks[n].length) - hooks[n] = hooks[n].concat(options.hooks[n]); - - executeHooks(hooks.processOptions, [options]); - } - - function setData(d) { - series = parseData(d); - fillInSeriesOptions(); - processData(); - } - - function parseData(d) { - var res = []; - for (var i = 0; i < d.length; ++i) { - var s = $.extend(true, {}, options.series); - - if (d[i].data != null) { - s.data = d[i].data; // move the data instead of deep-copy - delete d[i].data; - - $.extend(true, s, d[i]); - - d[i].data = s.data; - } - else - s.data = d[i]; - res.push(s); - } - - return res; - } - - function axisNumber(obj, coord) { - var a = obj[coord + "axis"]; - if (typeof a == "object") // if we got a real axis, extract number - a = a.n; - if (typeof a != "number") - a = 1; // default to first axis - return a; - } - - function allAxes() { - // return flat array without annoying null entries - return $.grep(xaxes.concat(yaxes), function (a) { return a; }); - } - - function canvasToAxisCoords(pos) { - // return an object with x/y corresponding to all used axes - var res = {}, i, axis; - for (i = 0; i < xaxes.length; ++i) { - axis = xaxes[i]; - if (axis && axis.used) - res["x" + axis.n] = axis.c2p(pos.left); - } - - for (i = 0; i < yaxes.length; ++i) { - axis = yaxes[i]; - if (axis && axis.used) - res["y" + axis.n] = axis.c2p(pos.top); - } - - if (res.x1 !== undefined) - res.x = res.x1; - if (res.y1 !== undefined) - res.y = res.y1; - - return res; - } - - function axisToCanvasCoords(pos) { - // get canvas coords from the first pair of x/y found in pos - var res = {}, i, axis, key; - - for (i = 0; i < xaxes.length; ++i) { - axis = xaxes[i]; - if (axis && axis.used) { - key = "x" + axis.n; - if (pos[key] == null && axis.n == 1) - key = "x"; - - if (pos[key] != null) { - res.left = axis.p2c(pos[key]); - break; - } - } - } - - for (i = 0; i < yaxes.length; ++i) { - axis = yaxes[i]; - if (axis && axis.used) { - key = "y" + axis.n; - if (pos[key] == null && axis.n == 1) - key = "y"; - - if (pos[key] != null) { - res.top = axis.p2c(pos[key]); - break; - } - } - } - - return res; - } - - function getOrCreateAxis(axes, number) { - if (!axes[number - 1]) - axes[number - 1] = { - n: number, // save the number for future reference - direction: axes == xaxes ? "x" : "y", - options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis) - }; - - return axes[number - 1]; - } - - function fillInSeriesOptions() { - var i; - - // collect what we already got of colors - var neededColors = series.length, - usedColors = [], - assignedColors = []; - for (i = 0; i < series.length; ++i) { - var sc = series[i].color; - if (sc != null) { - --neededColors; - if (typeof sc == "number") - assignedColors.push(sc); - else - usedColors.push($.color.parse(series[i].color)); - } - } - - // we might need to generate more colors if higher indices - // are assigned - for (i = 0; i < assignedColors.length; ++i) { - neededColors = Math.max(neededColors, assignedColors[i] + 1); - } - - // produce colors as needed - var colors = [], variation = 0; - i = 0; - while (colors.length < neededColors) { - var c; - if (options.colors.length == i) // check degenerate case - c = $.color.make(100, 100, 100); - else - c = $.color.parse(options.colors[i]); - - // vary color if needed - var sign = variation % 2 == 1 ? -1 : 1; - c.scale('rgb', 1 + sign * Math.ceil(variation / 2) * 0.2) - - // FIXME: if we're getting to close to something else, - // we should probably skip this one - colors.push(c); - - ++i; - if (i >= options.colors.length) { - i = 0; - ++variation; - } - } - - // fill in the options - var colori = 0, s; - for (i = 0; i < series.length; ++i) { - s = series[i]; - - // assign colors - if (s.color == null) { - s.color = colors[colori].toString(); - ++colori; - } - else if (typeof s.color == "number") - s.color = colors[s.color].toString(); - - // turn on lines automatically in case nothing is set - if (s.lines.show == null) { - var v, show = true; - for (v in s) - if (s[v] && s[v].show) { - show = false; - break; - } - if (show) - s.lines.show = true; - } - - // setup axes - s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x")); - s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y")); - } - } - - function processData() { - var topSentry = Number.POSITIVE_INFINITY, - bottomSentry = Number.NEGATIVE_INFINITY, - fakeInfinity = Number.MAX_VALUE, - i, j, k, m, length, - s, points, ps, x, y, axis, val, f, p; - - function updateAxis(axis, min, max) { - if (min < axis.datamin && min != -fakeInfinity) - axis.datamin = min; - if (max > axis.datamax && max != fakeInfinity) - axis.datamax = max; - } - - $.each(allAxes(), function (_, axis) { - // init axis - axis.datamin = topSentry; - axis.datamax = bottomSentry; - axis.used = false; - }); - - for (i = 0; i < series.length; ++i) { - s = series[i]; - s.datapoints = { points: [] }; - - executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]); - } - - // first pass: clean and copy data - for (i = 0; i < series.length; ++i) { - s = series[i]; - - var data = s.data, format = s.datapoints.format; - - if (!format) { - format = []; - // find out how to copy - format.push({ x: true, number: true, required: true }); - format.push({ y: true, number: true, required: true }); - - if (s.bars.show || (s.lines.show && s.lines.fill)) { - format.push({ y: true, number: true, required: false, defaultValue: 0 }); - if (s.bars.horizontal) { - delete format[format.length - 1].y; - format[format.length - 1].x = true; - } - } - - s.datapoints.format = format; - } - - if (s.datapoints.pointsize != null) - continue; // already filled in - - s.datapoints.pointsize = format.length; - - ps = s.datapoints.pointsize; - points = s.datapoints.points; - - let insertSteps = s.lines.show && s.lines.steps; - s.xaxis.used = s.yaxis.used = true; - - for (j = k = 0; j < data.length; ++j, k += ps) { - p = data[j]; - - var nullify = p == null; - if (!nullify) { - for (m = 0; m < ps; ++m) { - val = p[m]; - f = format[m]; - - if (f) { - if (f.number && val != null) { - val = +val; // convert to number - if (isNaN(val)) - val = null; - else if (val == Infinity) - val = fakeInfinity; - else if (val == -Infinity) - val = -fakeInfinity; - } - - if (val == null) { - if (f.required) - nullify = true; - - if (f.defaultValue != null) - val = f.defaultValue; - } - } - - points[k + m] = val; - } - } - - if (nullify) { - for (m = 0; m < ps; ++m) { - val = points[k + m]; - if (val != null) { - f = format[m]; - // extract min/max info - if (f.x) - updateAxis(s.xaxis, val, val); - if (f.y) - updateAxis(s.yaxis, val, val); - } - points[k + m] = null; - } - } - else { - // a little bit of line specific stuff that - // perhaps shouldn't be here, but lacking - // better means... - if (insertSteps && k > 0 - && points[k - ps] != null - && points[k - ps] != points[k] - && points[k - ps + 1] != points[k + 1]) { - // copy the point to make room for a middle point - for (m = 0; m < ps; ++m) - points[k + ps + m] = points[k + m]; - - // middle point has same y - points[k + 1] = points[k - ps + 1]; - - // we've added a point, better reflect that - k += ps; - } - } - } - } - - // give the hooks a chance to run - for (i = 0; i < series.length; ++i) { - s = series[i]; - - executeHooks(hooks.processDatapoints, [ s, s.datapoints]); - } - - // second pass: find datamax/datamin for auto-scaling - for (i = 0; i < series.length; ++i) { - s = series[i]; - points = s.datapoints.points, - ps = s.datapoints.pointsize; - - var xmin = topSentry, ymin = topSentry, - xmax = bottomSentry, ymax = bottomSentry; - - for (j = 0; j < points.length; j += ps) { - if (points[j] == null) - continue; - - for (m = 0; m < ps; ++m) { - val = points[j + m]; - f = format[m]; - if (!f || val == fakeInfinity || val == -fakeInfinity) - continue; - - if (f.x) { - if (val < xmin) - xmin = val; - if (val > xmax) - xmax = val; - } - if (f.y) { - if (val < ymin) - ymin = val; - if (val > ymax) - ymax = val; - } - } - } - - if (s.bars.show) { - // make sure we got room for the bar on the dancing floor - var delta = s.bars.align == "left" ? 0 : -s.bars.barWidth/2; - if (s.bars.horizontal) { - ymin += delta; - ymax += delta + s.bars.barWidth; - } - else { - xmin += delta; - xmax += delta + s.bars.barWidth; - } - } - - updateAxis(s.xaxis, xmin, xmax); - updateAxis(s.yaxis, ymin, ymax); - } - - $.each(allAxes(), function (_, axis) { - if (axis.datamin == topSentry) - axis.datamin = null; - if (axis.datamax == bottomSentry) - axis.datamax = null; - }); - } - - function makeCanvas(skipPositioning, cls) { - var c = document.createElement('canvas'); - c.className = cls; - c.width = canvasWidth; - c.height = canvasHeight; - - if (!skipPositioning) - $(c).css({ position: 'absolute', left: 0, top: 0 }); - - $(c).appendTo(placeholder); - - if (!c.getContext) // excanvas hack - c = window.G_vmlCanvasManager.initElement(c); - - // used for resetting in case we get replotted - c.getContext("2d").save(); - - return c; - } - - function getCanvasDimensions() { - canvasWidth = placeholder.width(); - canvasHeight = placeholder.height(); - - if (canvasWidth <= 0 || canvasHeight <= 0) - throw new Error("Invalid dimensions for plot, width = " + canvasWidth + ", height = " + canvasHeight); - } - - function resizeCanvas(c) { - // resizing should reset the state (excanvas seems to be - // buggy though) - if (c.width != canvasWidth) - c.width = canvasWidth; - - if (c.height != canvasHeight) - c.height = canvasHeight; - - // so try to get back to the initial state (even if it's - // gone now, this should be safe according to the spec) - var cctx = c.getContext("2d"); - cctx.restore(); - - // and save again - cctx.save(); - } - - function setupCanvases() { - var reused, - existingCanvas = placeholder.children("canvas.base"), - existingOverlay = placeholder.children("canvas.overlay"); - - if (existingCanvas.length == 0 || existingOverlay == 0) { - // init everything - - placeholder.html(""); // make sure placeholder is clear - - placeholder.css({ padding: 0 }); // padding messes up the positioning - - if (placeholder.css("position") == 'static') - placeholder.css("position", "relative"); // for positioning labels and overlay - - getCanvasDimensions(); - - canvas = makeCanvas(true, "base"); - overlay = makeCanvas(false, "overlay"); // overlay canvas for interactive features - - reused = false; - } - else { - // reuse existing elements - - canvas = existingCanvas.get(0); - overlay = existingOverlay.get(0); - - reused = true; - } - - ctx = canvas.getContext("2d"); - octx = overlay.getContext("2d"); - - // we include the canvas in the event holder too, because IE 7 - // sometimes has trouble with the stacking order - eventHolder = $([overlay, canvas]); - - if (reused) { - // run shutdown in the old plot object - placeholder.data("plot").shutdown(); - - // reset reused canvases - plot.resize(); - - // make sure overlay pixels are cleared (canvas is cleared when we redraw) - octx.clearRect(0, 0, canvasWidth, canvasHeight); - - // then whack any remaining obvious garbage left - eventHolder.unbind(); - placeholder.children().not([canvas, overlay]).remove(); - } - - // save in case we get replotted - placeholder.data("plot", plot); - } - - function bindEvents() { - // bind events - if (options.grid.hoverable) { - eventHolder.mousemove(onMouseMove); - eventHolder.mouseleave(onMouseLeave); - } - - if (options.grid.clickable) - eventHolder.click(onClick); - - executeHooks(hooks.bindEvents, [eventHolder]); - } - - function shutdown() { - if (redrawTimeout) - clearTimeout(redrawTimeout); - - eventHolder.unbind("mousemove", onMouseMove); - eventHolder.unbind("mouseleave", onMouseLeave); - eventHolder.unbind("click", onClick); - - executeHooks(hooks.shutdown, [eventHolder]); - } - - function setTransformationHelpers(axis) { - // set helper functions on the axis, assumes plot area - // has been computed already - - function identity(x) { return x; } - - var s, m, t = axis.options.transform || identity, - it = axis.options.inverseTransform; - - // precompute how much the axis is scaling a point - // in canvas space - if (axis.direction == "x") { - s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min)); - m = Math.min(t(axis.max), t(axis.min)); - } - else { - s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min)); - s = -s; - m = Math.max(t(axis.max), t(axis.min)); - } - - // data point to canvas coordinate - if (t == identity) // slight optimization - axis.p2c = function (p) { return (p - m) * s; }; - else - axis.p2c = function (p) { return (t(p) - m) * s; }; - // canvas coordinate to data point - if (!it) - axis.c2p = function (c) { return m + c / s; }; - else - axis.c2p = function (c) { return it(m + c / s); }; - } - - function measureTickLabels(axis) { - var opts = axis.options, i, ticks = axis.ticks || [], labels = [], - l, w = opts.labelWidth, h = opts.labelHeight, dummyDiv; - - function makeDummyDiv(labels, width) { - return $('
    ' + - '
    ' - + labels.join("") + '
    ') - .appendTo(placeholder); - } - - if (axis.direction == "x") { - // to avoid measuring the widths of the labels (it's slow), we - // construct fixed-size boxes and put the labels inside - // them, we don't need the exact figures and the - // fixed-size box content is easy to center - if (w == null) - w = Math.floor(canvasWidth / (ticks.length > 0 ? ticks.length : 1)); - - // measure x label heights - if (h == null) { - labels = []; - for (i = 0; i < ticks.length; ++i) { - l = ticks[i].label; - if (l) - labels.push('
    ' + l + '
    '); - } - - if (labels.length > 0) { - // stick them all in the same div and measure - // collective height - labels.push('
    '); - dummyDiv = makeDummyDiv(labels, "width:10000px;"); - h = dummyDiv.height(); - dummyDiv.remove(); - } - } - } - else if (w == null || h == null) { - // calculate y label dimensions - for (i = 0; i < ticks.length; ++i) { - l = ticks[i].label; - if (l) - labels.push('
    ' + l + '
    '); - } - - if (labels.length > 0) { - dummyDiv = makeDummyDiv(labels, ""); - if (w == null) - w = dummyDiv.children().width(); - if (h == null) - h = dummyDiv.find("div.tickLabel").height(); - dummyDiv.remove(); - } - } - - if (w == null) - w = 0; - if (h == null) - h = 0; - - axis.labelWidth = w; - axis.labelHeight = h; - } - - function allocateAxisBoxFirstPhase(axis) { - // find the bounding box of the axis by looking at label - // widths/heights and ticks, make room by diminishing the - // plotOffset - - var lw = axis.labelWidth, - lh = axis.labelHeight, - pos = axis.options.position, - tickLength = axis.options.tickLength, - axismargin = options.grid.axisMargin, - padding = options.grid.labelMargin, - all = axis.direction == "x" ? xaxes : yaxes, - index; - - // determine axis margin - var samePosition = $.grep(all, function (a) { - return a && a.options.position == pos && a.reserveSpace; - }); - if ($.inArray(axis, samePosition) == samePosition.length - 1) - axismargin = 0; // outermost - - // determine tick length - if we're innermost, we can use "full" - if (tickLength == null) - tickLength = "full"; - - var sameDirection = $.grep(all, function (a) { - return a && a.reserveSpace; - }); - - var innermost = $.inArray(axis, sameDirection) == 0; - if (!innermost && tickLength == "full") - tickLength = 5; - - if (!isNaN(+tickLength)) - padding += +tickLength; - - // compute box - if (axis.direction == "x") { - lh += padding; - - if (pos == "bottom") { - plotOffset.bottom += lh + axismargin; - axis.box = { top: canvasHeight - plotOffset.bottom, height: lh }; - } - else { - axis.box = { top: plotOffset.top + axismargin, height: lh }; - plotOffset.top += lh + axismargin; - } - } - else { - lw += padding; - - if (pos == "left") { - axis.box = { left: plotOffset.left + axismargin, width: lw }; - plotOffset.left += lw + axismargin; - } - else { - plotOffset.right += lw + axismargin; - axis.box = { left: canvasWidth - plotOffset.right, width: lw }; - } - } - - // save for future reference - axis.position = pos; - axis.tickLength = tickLength; - axis.box.padding = padding; - axis.innermost = innermost; - } - - function allocateAxisBoxSecondPhase(axis) { - // set remaining bounding box coordinates - if (axis.direction == "x") { - axis.box.left = plotOffset.left; - axis.box.width = plotWidth; - } - else { - axis.box.top = plotOffset.top; - axis.box.height = plotHeight; - } - } - - function setupGrid() { - var i, axes = allAxes(); - - // first calculate the plot and axis box dimensions - - $.each(axes, function (_, axis) { - axis.show = axis.options.show; - if (axis.show == null) - axis.show = axis.used; // by default an axis is visible if it's got data - - axis.reserveSpace = axis.show || axis.options.reserveSpace; - - setRange(axis); - }); - - let allocatedAxes = $.grep(axes, function (axis) { return axis.reserveSpace; }); - - plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = 0; - if (options.grid.show) { - $.each(allocatedAxes, function (_, axis) { - // make the ticks - setupTickGeneration(axis); - setTicks(axis); - snapRangeToTicks(axis, axis.ticks); - - // find labelWidth/Height for axis - measureTickLabels(axis); - }); - - // with all dimensions in house, we can compute the - // axis boxes, start from the outside (reverse order) - for (i = allocatedAxes.length - 1; i >= 0; --i) - allocateAxisBoxFirstPhase(allocatedAxes[i]); - - // make sure we've got enough space for things that - // might stick out - var minMargin = options.grid.minBorderMargin; - if (minMargin == null) { - minMargin = 0; - for (i = 0; i < series.length; ++i) - minMargin = Math.max(minMargin, series[i].points.radius + series[i].points.lineWidth/2); - } - - for (var a in plotOffset) { - plotOffset[a] += options.grid.borderWidth; - plotOffset[a] = Math.max(minMargin, plotOffset[a]); - } - } - - plotWidth = canvasWidth - plotOffset.left - plotOffset.right; - plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top; - - // now we got the proper plotWidth/Height, we can compute the scaling - $.each(axes, function (_, axis) { - setTransformationHelpers(axis); - }); - - if (options.grid.show) { - $.each(allocatedAxes, function (_, axis) { - allocateAxisBoxSecondPhase(axis); - }); - - insertAxisLabels(); - } - - insertLegend(); - } - - function setRange(axis) { - var opts = axis.options, - min = +(opts.min != null ? opts.min : axis.datamin), - max = +(opts.max != null ? opts.max : axis.datamax), - delta = max - min; - - if (delta == 0.0) { - // degenerate case - var widen = max == 0 ? 1 : 0.01; - - if (opts.min == null) - min -= widen; - // always widen max if we couldn't widen min to ensure we - // don't fall into min == max which doesn't work - if (opts.max == null || opts.min != null) - max += widen; - } - else { - // consider autoscaling - var margin = opts.autoscaleMargin; - if (margin != null) { - if (opts.min == null) { - min -= delta * margin; - // make sure we don't go below zero if all values - // are positive - if (min < 0 && axis.datamin != null && axis.datamin >= 0) - min = 0; - } - if (opts.max == null) { - max += delta * margin; - if (max > 0 && axis.datamax != null && axis.datamax <= 0) - max = 0; - } - } - } - axis.min = min; - axis.max = max; - } - - function setupTickGeneration(axis) { - var opts = axis.options; - - // estimate number of ticks - var noTicks; - if (typeof opts.ticks == "number" && opts.ticks > 0) - noTicks = opts.ticks; - else - // heuristic based on the model a*sqrt(x) fitted to - // some data points that seemed reasonable - noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? canvasWidth : canvasHeight); - - var delta = (axis.max - axis.min) / noTicks, - size, generator, unit, formatter, i, magn, norm; - - if (opts.mode == "time") { - // pretty handling of time - - // map of app. size of time units in milliseconds - var timeUnitSize = { - "second": 1000, - "minute": 60 * 1000, - "hour": 60 * 60 * 1000, - "day": 24 * 60 * 60 * 1000, - "month": 30 * 24 * 60 * 60 * 1000, - "year": 365.2425 * 24 * 60 * 60 * 1000 - }; - - - // the allowed tick sizes, after 1 year we use - // an integer algorithm - var spec = [ - [1, "second"], [2, "second"], [5, "second"], [10, "second"], - [30, "second"], - [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"], - [30, "minute"], - [1, "hour"], [2, "hour"], [4, "hour"], - [8, "hour"], [12, "hour"], - [1, "day"], [2, "day"], [3, "day"], - [0.25, "month"], [0.5, "month"], [1, "month"], - [2, "month"], [3, "month"], [6, "month"], - [1, "year"] - ]; - - var minSize = 0; - if (opts.minTickSize != null) { - if (typeof opts.tickSize == "number") - minSize = opts.tickSize; - else - minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]]; - } - - for (var i = 0; i < spec.length - 1; ++i) - if (delta < (spec[i][0] * timeUnitSize[spec[i][1]] - + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2 - && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) - break; - size = spec[i][0]; - unit = spec[i][1]; - - // special-case the possibility of several years - if (unit == "year") { - magn = Math.pow(10, Math.floor(Math.log(delta / timeUnitSize.year) / Math.LN10)); - norm = (delta / timeUnitSize.year) / magn; - if (norm < 1.5) - size = 1; - else if (norm < 3) - size = 2; - else if (norm < 7.5) - size = 5; - else - size = 10; - - size *= magn; - } - - axis.tickSize = opts.tickSize || [size, unit]; - - generator = function(axis) { - var ticks = [], - tickSize = axis.tickSize[0], unit = axis.tickSize[1], - d = new Date(axis.min); - - var step = tickSize * timeUnitSize[unit]; - - if (unit == "second") - d.setUTCSeconds(floorInBase(d.getUTCSeconds(), tickSize)); - if (unit == "minute") - d.setUTCMinutes(floorInBase(d.getUTCMinutes(), tickSize)); - if (unit == "hour") - d.setUTCHours(floorInBase(d.getUTCHours(), tickSize)); - if (unit == "month") - d.setUTCMonth(floorInBase(d.getUTCMonth(), tickSize)); - if (unit == "year") - d.setUTCFullYear(floorInBase(d.getUTCFullYear(), tickSize)); - - // reset smaller components - d.setUTCMilliseconds(0); - if (step >= timeUnitSize.minute) - d.setUTCSeconds(0); - if (step >= timeUnitSize.hour) - d.setUTCMinutes(0); - if (step >= timeUnitSize.day) - d.setUTCHours(0); - if (step >= timeUnitSize.day * 4) - d.setUTCDate(1); - if (step >= timeUnitSize.year) - d.setUTCMonth(0); - - - var carry = 0, v = Number.NaN, prev; - do { - prev = v; - v = d.getTime(); - ticks.push(v); - if (unit == "month") { - if (tickSize < 1) { - // a bit complicated - we'll divide the month - // up but we need to take care of fractions - // so we don't end up in the middle of a day - d.setUTCDate(1); - var start = d.getTime(); - d.setUTCMonth(d.getUTCMonth() + 1); - var end = d.getTime(); - d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize); - carry = d.getUTCHours(); - d.setUTCHours(0); - } - else - d.setUTCMonth(d.getUTCMonth() + tickSize); - } - else if (unit == "year") { - d.setUTCFullYear(d.getUTCFullYear() + tickSize); - } - else - d.setTime(v + step); - } while (v < axis.max && v != prev); - - return ticks; - }; - - formatter = function (v, axis) { - var d = new Date(v); - - // first check global format - if (opts.timeformat != null) - return $.plot.formatDate(d, opts.timeformat, opts.monthNames); - - var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]]; - var span = axis.max - axis.min; - var suffix = (opts.twelveHourClock) ? " %p" : ""; - - if (t < timeUnitSize.minute) - fmt = "%h:%M:%S" + suffix; - else if (t < timeUnitSize.day) { - if (span < 2 * timeUnitSize.day) - fmt = "%h:%M" + suffix; - else - fmt = "%b %d %h:%M" + suffix; - } - else if (t < timeUnitSize.month) - fmt = "%b %d"; - else if (t < timeUnitSize.year) { - if (span < timeUnitSize.year) - fmt = "%b"; - else - fmt = "%b %y"; - } - else - fmt = "%y"; - - return $.plot.formatDate(d, fmt, opts.monthNames); - }; - } - else { - // pretty rounding of base-10 numbers - var maxDec = opts.tickDecimals; - var dec = -Math.floor(Math.log(delta) / Math.LN10); - if (maxDec != null && dec > maxDec) - dec = maxDec; - - magn = Math.pow(10, -dec); - norm = delta / magn; // norm is between 1.0 and 10.0 - - if (norm < 1.5) - size = 1; - else if (norm < 3) { - size = 2; - // special case for 2.5, requires an extra decimal - if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) { - size = 2.5; - ++dec; - } - } - else if (norm < 7.5) - size = 5; - else - size = 10; - - size *= magn; - - if (opts.minTickSize != null && size < opts.minTickSize) - size = opts.minTickSize; - - axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec); - axis.tickSize = opts.tickSize || size; - - generator = function (axis) { - var ticks = []; - - // spew out all possible ticks - var start = floorInBase(axis.min, axis.tickSize), - i = 0, v = Number.NaN, prev; - do { - prev = v; - v = start + i * axis.tickSize; - ticks.push(v); - ++i; - } while (v < axis.max && v != prev); - return ticks; - }; - - formatter = function (v, axis) { - return v.toFixed(axis.tickDecimals); - }; - } - - if (opts.alignTicksWithAxis != null) { - var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1]; - if (otherAxis && otherAxis.used && otherAxis != axis) { - // consider snapping min/max to outermost nice ticks - var niceTicks = generator(axis); - if (niceTicks.length > 0) { - if (opts.min == null) - axis.min = Math.min(axis.min, niceTicks[0]); - if (opts.max == null && niceTicks.length > 1) - axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]); - } - - generator = function (axis) { - // copy ticks, scaled to this axis - var ticks = [], v, i; - for (i = 0; i < otherAxis.ticks.length; ++i) { - v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min); - v = axis.min + v * (axis.max - axis.min); - ticks.push(v); - } - return ticks; - }; - - // we might need an extra decimal since forced - // ticks don't necessarily fit naturally - if (axis.mode != "time" && opts.tickDecimals == null) { - var extraDec = Math.max(0, -Math.floor(Math.log(delta) / Math.LN10) + 1), - ts = generator(axis); - - // only proceed if the tick interval rounded - // with an extra decimal doesn't give us a - // zero at end - if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec)))) - axis.tickDecimals = extraDec; - } - } - } - - axis.tickGenerator = generator; - if ($.isFunction(opts.tickFormatter)) - axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); }; - else - axis.tickFormatter = formatter; - } - - function setTicks(axis) { - var oticks = axis.options.ticks, ticks = []; - if (oticks == null || (typeof oticks == "number" && oticks > 0)) - ticks = axis.tickGenerator(axis); - else if (oticks) { - if ($.isFunction(oticks)) - // generate the ticks - ticks = oticks({ min: axis.min, max: axis.max }); - else - ticks = oticks; - } - - // clean up/labelify the supplied ticks, copy them over - var i, v; - axis.ticks = []; - for (i = 0; i < ticks.length; ++i) { - var label = null; - var t = ticks[i]; - if (typeof t == "object") { - v = +t[0]; - if (t.length > 1) - label = t[1]; - } - else - v = +t; - if (label == null) - label = axis.tickFormatter(v, axis); - if (!isNaN(v)) - axis.ticks.push({ v: v, label: label }); - } - } - - function snapRangeToTicks(axis, ticks) { - if (axis.options.autoscaleMargin && ticks.length > 0) { - // snap to ticks - if (axis.options.min == null) - axis.min = Math.min(axis.min, ticks[0].v); - if (axis.options.max == null && ticks.length > 1) - axis.max = Math.max(axis.max, ticks[ticks.length - 1].v); - } - } - - function draw() { - ctx.clearRect(0, 0, canvasWidth, canvasHeight); - - var grid = options.grid; - - // draw background, if any - if (grid.show && grid.backgroundColor) - drawBackground(); - - if (grid.show && !grid.aboveData) - drawGrid(); - - for (var i = 0; i < series.length; ++i) { - executeHooks(hooks.drawSeries, [ctx, series[i]]); - drawSeries(series[i]); - } - - executeHooks(hooks.draw, [ctx]); - - if (grid.show && grid.aboveData) - drawGrid(); - } - - function extractRange(ranges, coord) { - var axis, from, to, key, axes = allAxes(); - - for (i = 0; i < axes.length; ++i) { - axis = axes[i]; - if (axis.direction == coord) { - key = coord + axis.n + "axis"; - if (!ranges[key] && axis.n == 1) - key = coord + "axis"; // support x1axis as xaxis - if (ranges[key]) { - from = ranges[key].from; - to = ranges[key].to; - break; - } - } - } - - // backwards-compat stuff - to be removed in future - if (!ranges[key]) { - axis = coord == "x" ? xaxes[0] : yaxes[0]; - from = ranges[coord + "1"]; - to = ranges[coord + "2"]; - } - - // auto-reverse as an added bonus - if (from != null && to != null && from > to) { - var tmp = from; - from = to; - to = tmp; - } - - return { from: from, to: to, axis: axis }; - } - - function drawBackground() { - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)"); - ctx.fillRect(0, 0, plotWidth, plotHeight); - ctx.restore(); - } - - function drawGrid() { - var i; - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - // draw markings - var markings = options.grid.markings; - if (markings) { - if ($.isFunction(markings)) { - var axes = plot.getAxes(); - // xmin etc. is backwards compatibility, to be - // removed in the future - axes.xmin = axes.xaxis.min; - axes.xmax = axes.xaxis.max; - axes.ymin = axes.yaxis.min; - axes.ymax = axes.yaxis.max; - - markings = markings(axes); - } - - for (i = 0; i < markings.length; ++i) { - var m = markings[i], - xrange = extractRange(m, "x"), - yrange = extractRange(m, "y"); - - // fill in missing - if (xrange.from == null) - xrange.from = xrange.axis.min; - if (xrange.to == null) - xrange.to = xrange.axis.max; - if (yrange.from == null) - yrange.from = yrange.axis.min; - if (yrange.to == null) - yrange.to = yrange.axis.max; - - // clip - if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max || - yrange.to < yrange.axis.min || yrange.from > yrange.axis.max) - continue; - - xrange.from = Math.max(xrange.from, xrange.axis.min); - xrange.to = Math.min(xrange.to, xrange.axis.max); - yrange.from = Math.max(yrange.from, yrange.axis.min); - yrange.to = Math.min(yrange.to, yrange.axis.max); - - if (xrange.from == xrange.to && yrange.from == yrange.to) - continue; - - // then draw - xrange.from = xrange.axis.p2c(xrange.from); - xrange.to = xrange.axis.p2c(xrange.to); - yrange.from = yrange.axis.p2c(yrange.from); - yrange.to = yrange.axis.p2c(yrange.to); - - if (xrange.from == xrange.to || yrange.from == yrange.to) { - // draw line - ctx.beginPath(); - ctx.strokeStyle = m.color || options.grid.markingsColor; - ctx.lineWidth = m.lineWidth || options.grid.markingsLineWidth; - ctx.moveTo(xrange.from, yrange.from); - ctx.lineTo(xrange.to, yrange.to); - ctx.stroke(); - } - else { - // fill area - ctx.fillStyle = m.color || options.grid.markingsColor; - ctx.fillRect(xrange.from, yrange.to, - xrange.to - xrange.from, - yrange.from - yrange.to); - } - } - } - - // draw the ticks - var axes = allAxes(), bw = options.grid.borderWidth; - - for (var j = 0; j < axes.length; ++j) { - var axis = axes[j], box = axis.box, - t = axis.tickLength, x, y, xoff, yoff; - if (!axis.show || axis.ticks.length == 0) - continue - - ctx.strokeStyle = axis.options.tickColor || $.color.parse(axis.options.color).scale('a', 0.22).toString(); - ctx.lineWidth = 1; - - // find the edges - if (axis.direction == "x") { - x = 0; - if (t == "full") - y = (axis.position == "top" ? 0 : plotHeight); - else - y = box.top - plotOffset.top + (axis.position == "top" ? box.height : 0); - } - else { - y = 0; - if (t == "full") - x = (axis.position == "left" ? 0 : plotWidth); - else - x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0); - } - - // draw tick bar - if (!axis.innermost) { - ctx.beginPath(); - xoff = yoff = 0; - if (axis.direction == "x") - xoff = plotWidth; - else - yoff = plotHeight; - - if (ctx.lineWidth == 1) { - x = Math.floor(x) + 0.5; - y = Math.floor(y) + 0.5; - } - - ctx.moveTo(x, y); - ctx.lineTo(x + xoff, y + yoff); - ctx.stroke(); - } - - // draw ticks - ctx.beginPath(); - for (i = 0; i < axis.ticks.length; ++i) { - var v = axis.ticks[i].v; - - xoff = yoff = 0; - - if (v < axis.min || v > axis.max - // skip those lying on the axes if we got a border - || (t == "full" && bw > 0 - && (v == axis.min || v == axis.max))) - continue; - - if (axis.direction == "x") { - x = axis.p2c(v); - yoff = t == "full" ? -plotHeight : t; - - if (axis.position == "top") - yoff = -yoff; - } - else { - y = axis.p2c(v); - xoff = t == "full" ? -plotWidth : t; - - if (axis.position == "left") - xoff = -xoff; - } - - if (ctx.lineWidth == 1) { - if (axis.direction == "x") - x = Math.floor(x) + 0.5; - else - y = Math.floor(y) + 0.5; - } - - ctx.moveTo(x, y); - ctx.lineTo(x + xoff, y + yoff); - } - - ctx.stroke(); - } - - - // draw border - if (bw) { - ctx.lineWidth = bw; - ctx.strokeStyle = options.grid.borderColor; - ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw); - } - - ctx.restore(); - } - - function insertAxisLabels() { - placeholder.find(".tickLabels").remove(); - - var html = ['
    ']; - - var axes = allAxes(); - for (var j = 0; j < axes.length; ++j) { - var axis = axes[j], box = axis.box; - if (!axis.show) - continue; - //debug: html.push('
    ') - html.push('
    '); - for (var i = 0; i < axis.ticks.length; ++i) { - var tick = axis.ticks[i]; - if (!tick.label || tick.v < axis.min || tick.v > axis.max) - continue; - - var pos = {}, align; - - if (axis.direction == "x") { - align = "center"; - pos.left = Math.round(plotOffset.left + axis.p2c(tick.v) - axis.labelWidth/2); - if (axis.position == "bottom") - pos.top = box.top + box.padding; - else - pos.bottom = canvasHeight - (box.top + box.height - box.padding); - } - else { - pos.top = Math.round(plotOffset.top + axis.p2c(tick.v) - axis.labelHeight/2); - if (axis.position == "left") { - pos.right = canvasWidth - (box.left + box.width - box.padding) - align = "right"; - } - else { - pos.left = box.left + box.padding; - align = "left"; - } - } - - pos.width = axis.labelWidth; - - var style = ["position:absolute", "text-align:" + align ]; - for (var a in pos) - style.push(a + ":" + pos[a] + "px") - - html.push('
    ' + tick.label + '
    '); - } - html.push('
    '); - } - - html.push('
    '); - - placeholder.append(html.join("")); - } - - function drawSeries(series) { - if (series.lines.show) - drawSeriesLines(series); - if (series.bars.show) - drawSeriesBars(series); - if (series.points.show) - drawSeriesPoints(series); - } - - function drawSeriesLines(series) { - function plotLine(datapoints, xoffset, yoffset, axisx, axisy) { - var points = datapoints.points, - ps = datapoints.pointsize, - prevx = null, prevy = null; - - ctx.beginPath(); - for (var i = ps; i < points.length; i += ps) { - var x1 = points[i - ps], y1 = points[i - ps + 1], - x2 = points[i], y2 = points[i + 1]; - - if (x1 == null || x2 == null) - continue; - - // clip with ymin - if (y1 <= y2 && y1 < axisy.min) { - if (y2 < axisy.min) - continue; // line segment is outside - // compute new intersection point - x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.min; - } - else if (y2 <= y1 && y2 < axisy.min) { - if (y1 < axisy.min) - continue; - x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.min; - } - - // clip with ymax - if (y1 >= y2 && y1 > axisy.max) { - if (y2 > axisy.max) - continue; - x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.max; - } - else if (y2 >= y1 && y2 > axisy.max) { - if (y1 > axisy.max) - continue; - x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.max; - } - - // clip with xmin - if (x1 <= x2 && x1 < axisx.min) { - if (x2 < axisx.min) - continue; - y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.min; - } - else if (x2 <= x1 && x2 < axisx.min) { - if (x1 < axisx.min) - continue; - y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.min; - } - - // clip with xmax - if (x1 >= x2 && x1 > axisx.max) { - if (x2 > axisx.max) - continue; - y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.max; - } - else if (x2 >= x1 && x2 > axisx.max) { - if (x1 > axisx.max) - continue; - y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.max; - } - - if (x1 != prevx || y1 != prevy) - ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset); - - prevx = x2; - prevy = y2; - ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset); - } - ctx.stroke(); - } - - function plotLineArea(datapoints, axisx, axisy) { - var points = datapoints.points, - ps = datapoints.pointsize, - bottom = Math.min(Math.max(0, axisy.min), axisy.max), - i = 0, top, areaOpen = false, - ypos = 1, segmentStart = 0, segmentEnd = 0; - - // we process each segment in two turns, first forward - // direction to sketch out top, then once we hit the - // end we go backwards to sketch the bottom - while (true) { - if (ps > 0 && i > points.length + ps) - break; - - i += ps; // ps is negative if going backwards - - var x1 = points[i - ps], - y1 = points[i - ps + ypos], - x2 = points[i], y2 = points[i + ypos]; - - if (areaOpen) { - if (ps > 0 && x1 != null && x2 == null) { - // at turning point - segmentEnd = i; - ps = -ps; - ypos = 2; - continue; - } - - if (ps < 0 && i == segmentStart + ps) { - // done with the reverse sweep - ctx.fill(); - areaOpen = false; - ps = -ps; - ypos = 1; - i = segmentStart = segmentEnd + ps; - continue; - } - } - - if (x1 == null || x2 == null) - continue; - - // clip x values - - // clip with xmin - if (x1 <= x2 && x1 < axisx.min) { - if (x2 < axisx.min) - continue; - y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.min; - } - else if (x2 <= x1 && x2 < axisx.min) { - if (x1 < axisx.min) - continue; - y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.min; - } - - // clip with xmax - if (x1 >= x2 && x1 > axisx.max) { - if (x2 > axisx.max) - continue; - y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.max; - } - else if (x2 >= x1 && x2 > axisx.max) { - if (x1 > axisx.max) - continue; - y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.max; - } - - if (!areaOpen) { - // open area - ctx.beginPath(); - ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom)); - areaOpen = true; - } - - // now first check the case where both is outside - if (y1 >= axisy.max && y2 >= axisy.max) { - ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max)); - ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max)); - continue; - } - else if (y1 <= axisy.min && y2 <= axisy.min) { - ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min)); - ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min)); - continue; - } - - // else it's a bit more complicated, there might - // be a flat maxed out rectangle first, then a - // triangular cutout or reverse; to find these - // keep track of the current x values - var x1old = x1, x2old = x2; - - // clip the y values, without shortcutting, we - // go through all cases in turn - - // clip with ymin - if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) { - x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.min; - } - else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) { - x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.min; - } - - // clip with ymax - if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) { - x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.max; - } - else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) { - x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.max; - } - - // if the x value was changed we got a rectangle - // to fill - if (x1 != x1old) { - ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1)); - // it goes to (x1, y1), but we fill that below - } - - // fill triangular section, this sometimes result - // in redundant points if (x1, y1) hasn't changed - // from previous line to, but we just ignore that - ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1)); - ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); - - // fill the other rectangle if it's there - if (x2 != x2old) { - ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); - ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2)); - } - } - } - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - ctx.lineJoin = "round"; - - var lw = series.lines.lineWidth, - sw = series.shadowSize; - // FIXME: consider another form of shadow when filling is turned on - if (lw > 0 && sw > 0) { - // draw shadow as a thick and thin line with transparency - ctx.lineWidth = sw; - ctx.strokeStyle = "rgba(0,0,0,0.1)"; - // position shadow at angle from the mid of line - var angle = Math.PI/18; - plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis); - ctx.lineWidth = sw/2; - plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis); - } - - ctx.lineWidth = lw; - ctx.strokeStyle = series.color; - var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight); - if (fillStyle) { - ctx.fillStyle = fillStyle; - plotLineArea(series.datapoints, series.xaxis, series.yaxis); - } - - if (lw > 0) - plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis); - ctx.restore(); - } - - function drawSeriesPoints(series) { - function plotPoints(datapoints, radius, fillStyle, offset, shadow, axisx, axisy, symbol) { - var points = datapoints.points, ps = datapoints.pointsize; - - for (var i = 0; i < points.length; i += ps) { - var x = points[i], y = points[i + 1]; - if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) - continue; - - ctx.beginPath(); - x = axisx.p2c(x); - y = axisy.p2c(y) + offset; - if (symbol == "circle") - ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false); - else - symbol(ctx, x, y, radius, shadow); - ctx.closePath(); - - if (fillStyle) { - ctx.fillStyle = fillStyle; - ctx.fill(); - } - ctx.stroke(); - } - } - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - var lw = series.points.lineWidth, - sw = series.shadowSize, - radius = series.points.radius, - symbol = series.points.symbol; - if (lw > 0 && sw > 0) { - // draw shadow in two steps - var w = sw / 2; - ctx.lineWidth = w; - ctx.strokeStyle = "rgba(0,0,0,0.1)"; - plotPoints(series.datapoints, radius, null, w + w/2, true, - series.xaxis, series.yaxis, symbol); - - ctx.strokeStyle = "rgba(0,0,0,0.2)"; - plotPoints(series.datapoints, radius, null, w/2, true, - series.xaxis, series.yaxis, symbol); - } - - ctx.lineWidth = lw; - ctx.strokeStyle = series.color; - plotPoints(series.datapoints, radius, - getFillStyle(series.points, series.color), 0, false, - series.xaxis, series.yaxis, symbol); - ctx.restore(); - } - - function drawBar(x, y, b, barLeft, barRight, offset, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) { - var left, right, bottom, top, - drawLeft, drawRight, drawTop, drawBottom, - tmp; - - // in horizontal mode, we start the bar from the left - // instead of from the bottom so it appears to be - // horizontal rather than vertical - if (horizontal) { - drawBottom = drawRight = drawTop = true; - drawLeft = false; - left = b; - right = x; - top = y + barLeft; - bottom = y + barRight; - - // account for negative bars - if (right < left) { - tmp = right; - right = left; - left = tmp; - drawLeft = true; - drawRight = false; - } - } - else { - drawLeft = drawRight = drawTop = true; - drawBottom = false; - left = x + barLeft; - right = x + barRight; - bottom = b; - top = y; - - // account for negative bars - if (top < bottom) { - tmp = top; - top = bottom; - bottom = tmp; - drawBottom = true; - drawTop = false; - } - } - - // clip - if (right < axisx.min || left > axisx.max || - top < axisy.min || bottom > axisy.max) - return; - - if (left < axisx.min) { - left = axisx.min; - drawLeft = false; - } - - if (right > axisx.max) { - right = axisx.max; - drawRight = false; - } - - if (bottom < axisy.min) { - bottom = axisy.min; - drawBottom = false; - } - - if (top > axisy.max) { - top = axisy.max; - drawTop = false; - } - - left = axisx.p2c(left); - bottom = axisy.p2c(bottom); - right = axisx.p2c(right); - top = axisy.p2c(top); - - // fill the bar - if (fillStyleCallback) { - c.beginPath(); - c.moveTo(left, bottom); - c.lineTo(left, top); - c.lineTo(right, top); - c.lineTo(right, bottom); - c.fillStyle = fillStyleCallback(bottom, top); - c.fill(); - } - - // draw outline - if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) { - c.beginPath(); - - // FIXME: inline moveTo is buggy with excanvas - c.moveTo(left, bottom + offset); - if (drawLeft) - c.lineTo(left, top + offset); - else - c.moveTo(left, top + offset); - if (drawTop) - c.lineTo(right, top + offset); - else - c.moveTo(right, top + offset); - if (drawRight) - c.lineTo(right, bottom + offset); - else - c.moveTo(right, bottom + offset); - if (drawBottom) - c.lineTo(left, bottom + offset); - else - c.moveTo(left, bottom + offset); - c.stroke(); - } - } - - function drawSeriesBars(series) { - function plotBars(datapoints, barLeft, barRight, offset, fillStyleCallback, axisx, axisy) { - var points = datapoints.points, ps = datapoints.pointsize; - - for (var i = 0; i < points.length; i += ps) { - if (points[i] == null) - continue; - drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, offset, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth); - } - } - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - // FIXME: figure out a way to add shadows (for instance along the right edge) - ctx.lineWidth = series.bars.lineWidth; - ctx.strokeStyle = series.color; - var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2; - var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null; - plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, 0, fillStyleCallback, series.xaxis, series.yaxis); - ctx.restore(); - } - - function getFillStyle(filloptions, seriesColor, bottom, top) { - var fill = filloptions.fill; - if (!fill) - return null; - - if (filloptions.fillColor) - return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor); - - var c = $.color.parse(seriesColor); - c.a = typeof fill == "number" ? fill : 0.4; - c.normalize(); - return c.toString(); - } - - function insertLegend() { - placeholder.find(".legend").remove(); - - if (!options.legend.show) - return; - - var fragments = [], rowStarted = false, - lf = options.legend.labelFormatter, s, label; - for (var i = 0; i < series.length; ++i) { - s = series[i]; - label = s.label; - if (!label) - continue; - - if (i % options.legend.noColumns == 0) { - if (rowStarted) - fragments.push(''); - fragments.push('
    '); - rowStarted = true; - } - - if (lf) - label = lf(label, s); - - fragments.push( - '' + - ''); - } - if (rowStarted) - fragments.push(''); - - if (fragments.length == 0) - return; - - var table = '
    ' + label + '
    ' + fragments.join("") + '
    '; - if (options.legend.container != null) - $(options.legend.container).html(table); - else { - var pos = "", - p = options.legend.position, - m = options.legend.margin; - if (m[0] == null) - m = [m, m]; - if (p.charAt(0) == "n") - pos += 'top:' + (m[1] + plotOffset.top) + 'px;'; - else if (p.charAt(0) == "s") - pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;'; - if (p.charAt(1) == "e") - pos += 'right:' + (m[0] + plotOffset.right) + 'px;'; - else if (p.charAt(1) == "w") - pos += 'left:' + (m[0] + plotOffset.left) + 'px;'; - var legend = $('
    ' + table.replace('style="', 'style="position:absolute;' + pos +';') + '
    ').appendTo(placeholder); - if (options.legend.backgroundOpacity != 0.0) { - // put in the transparent background - // separately to avoid blended labels and - // label boxes - var c = options.legend.backgroundColor; - if (c == null) { - c = options.grid.backgroundColor; - if (c && typeof c == "string") - c = $.color.parse(c); - else - c = $.color.extract(legend, 'background-color'); - c.a = 1; - c = c.toString(); - } - var div = legend.children(); - $('
    ').prependTo(legend).css('opacity', options.legend.backgroundOpacity); - } - } - } - - - // interactive features - - var highlights = [], - redrawTimeout = null; - - // returns the data item the mouse is over, or null if none is found - function findNearbyItem(mouseX, mouseY, seriesFilter) { - var maxDistance = options.grid.mouseActiveRadius, - smallestDistance = maxDistance * maxDistance + 1, - item = null, foundPoint = false, i, j; - - for (i = series.length - 1; i >= 0; --i) { - if (!seriesFilter(series[i])) - continue; - - var s = series[i], - axisx = s.xaxis, - axisy = s.yaxis, - points = s.datapoints.points, - ps = s.datapoints.pointsize, - mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster - my = axisy.c2p(mouseY), - maxx = maxDistance / axisx.scale, - maxy = maxDistance / axisy.scale; - - // with inverse transforms, we can't use the maxx/maxy - // optimization, sadly - if (axisx.options.inverseTransform) - maxx = Number.MAX_VALUE; - if (axisy.options.inverseTransform) - maxy = Number.MAX_VALUE; - - if (s.lines.show || s.points.show) { - for (j = 0; j < points.length; j += ps) { - var x = points[j], y = points[j + 1]; - if (x == null) - continue; - - // For points and lines, the cursor must be within a - // certain distance to the data point - if (x - mx > maxx || x - mx < -maxx || - y - my > maxy || y - my < -maxy) - continue; - - // We have to calculate distances in pixels, not in - // data units, because the scales of the axes may be different - var dx = Math.abs(axisx.p2c(x) - mouseX), - dy = Math.abs(axisy.p2c(y) - mouseY), - dist = dx * dx + dy * dy; // we save the sqrt - - // use <= to ensure last point takes precedence - // (last generally means on top of) - if (dist < smallestDistance) { - smallestDistance = dist; - item = [i, j / ps]; - } - } - } - - if (s.bars.show && !item) { // no other point can be nearby - var barLeft = s.bars.align == "left" ? 0 : -s.bars.barWidth/2, - barRight = barLeft + s.bars.barWidth; - - for (j = 0; j < points.length; j += ps) { - var x = points[j], y = points[j + 1], b = points[j + 2]; - if (x == null) - continue; - - // for a bar graph, the cursor must be inside the bar - if (series[i].bars.horizontal ? - (mx <= Math.max(b, x) && mx >= Math.min(b, x) && - my >= y + barLeft && my <= y + barRight) : - (mx >= x + barLeft && mx <= x + barRight && - my >= Math.min(b, y) && my <= Math.max(b, y))) - item = [i, j / ps]; - } - } - } - - if (item) { - i = item[0]; - j = item[1]; - ps = series[i].datapoints.pointsize; - - return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps), - dataIndex: j, - series: series[i], - seriesIndex: i }; - } - - return null; - } - - function onMouseMove(e) { - if (options.grid.hoverable) - triggerClickHoverEvent("plothover", e, - function (s) { return s["hoverable"] != false; }); - } - - function onMouseLeave(e) { - if (options.grid.hoverable) - triggerClickHoverEvent("plothover", e, - function (s) { return false; }); - } - - function onClick(e) { - triggerClickHoverEvent("plotclick", e, - function (s) { return s["clickable"] != false; }); - } - - // trigger click or hover event (they send the same parameters - // so we share their code) - function triggerClickHoverEvent(eventname, event, seriesFilter) { - var offset = eventHolder.offset(), - canvasX = event.pageX - offset.left - plotOffset.left, - canvasY = event.pageY - offset.top - plotOffset.top, - pos = canvasToAxisCoords({ left: canvasX, top: canvasY }); - - pos.pageX = event.pageX; - pos.pageY = event.pageY; - - var item = findNearbyItem(canvasX, canvasY, seriesFilter); - - if (item) { - // fill in mouse pos for any listeners out there - item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left); - item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top); - } - - if (options.grid.autoHighlight) { - // clear auto-highlights - for (var i = 0; i < highlights.length; ++i) { - var h = highlights[i]; - if (h.auto == eventname && - !(item && h.series == item.series && - h.point[0] == item.datapoint[0] && - h.point[1] == item.datapoint[1])) - unhighlight(h.series, h.point); - } - - if (item) - highlight(item.series, item.datapoint, eventname); - } - - placeholder.trigger(eventname, [ pos, item ]); - } - - function triggerRedrawOverlay() { - if (!redrawTimeout) - redrawTimeout = setTimeout(drawOverlay, 30); - } - - function drawOverlay() { - redrawTimeout = null; - - // draw highlights - octx.save(); - octx.clearRect(0, 0, canvasWidth, canvasHeight); - octx.translate(plotOffset.left, plotOffset.top); - - var i, hi; - for (i = 0; i < highlights.length; ++i) { - hi = highlights[i]; - - if (hi.series.bars.show) - drawBarHighlight(hi.series, hi.point); - else - drawPointHighlight(hi.series, hi.point); - } - octx.restore(); - - executeHooks(hooks.drawOverlay, [octx]); - } - - function highlight(s, point, auto) { - if (typeof s == "number") - s = series[s]; - - if (typeof point == "number") { - var ps = s.datapoints.pointsize; - point = s.datapoints.points.slice(ps * point, ps * (point + 1)); - } - - var i = indexOfHighlight(s, point); - if (i == -1) { - highlights.push({ series: s, point: point, auto: auto }); - - triggerRedrawOverlay(); - } - else if (!auto) - highlights[i].auto = false; - } - - function unhighlight(s, point) { - if (s == null && point == null) { - highlights = []; - triggerRedrawOverlay(); - } - - if (typeof s == "number") - s = series[s]; - - if (typeof point == "number") - point = s.data[point]; - - var i = indexOfHighlight(s, point); - if (i != -1) { - highlights.splice(i, 1); - - triggerRedrawOverlay(); - } - } - - function indexOfHighlight(s, p) { - for (var i = 0; i < highlights.length; ++i) { - var h = highlights[i]; - if (h.series == s && h.point[0] == p[0] - && h.point[1] == p[1]) - return i; - } - return -1; - } - - function drawPointHighlight(series, point) { - var x = point[0], y = point[1], - axisx = series.xaxis, axisy = series.yaxis; - - if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) - return; - - var pointRadius = series.points.radius + series.points.lineWidth / 2; - octx.lineWidth = pointRadius; - octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString(); - var radius = 1.5 * pointRadius, - x = axisx.p2c(x), - y = axisy.p2c(y); - - octx.beginPath(); - if (series.points.symbol == "circle") - octx.arc(x, y, radius, 0, 2 * Math.PI, false); - else - series.points.symbol(octx, x, y, radius, false); - octx.closePath(); - octx.stroke(); - } - - function drawBarHighlight(series, point) { - octx.lineWidth = series.bars.lineWidth; - octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString(); - var fillStyle = $.color.parse(series.color).scale('a', 0.5).toString(); - var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2; - drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth, - 0, function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth); - } - - function getColorOrGradient(spec, bottom, top, defaultColor) { - if (typeof spec == "string") - return spec; - else { - // assume this is a gradient spec; IE currently only - // supports a simple vertical gradient properly, so that's - // what we support too - var gradient = ctx.createLinearGradient(0, top, 0, bottom); - - for (var i = 0, l = spec.colors.length; i < l; ++i) { - var c = spec.colors[i]; - if (typeof c != "string") { - var co = $.color.parse(defaultColor); - if (c.brightness != null) - co = co.scale('rgb', c.brightness) - if (c.opacity != null) - co.a *= c.opacity; - c = co.toString(); - } - gradient.addColorStop(i / (l - 1), c); - } - - return gradient; - } - } - } - - $.plot = function(placeholder, data, options) { - //var t0 = new Date(); - var plot = new Plot($(placeholder), data, options, $.plot.plugins); - //(window.console ? console.log : alert)("time used (msecs): " + ((new Date()).getTime() - t0.getTime())); - return plot; - }; - - $.plot.version = "0.7"; - - $.plot.plugins = []; - - // returns a string with the date d formatted according to fmt - $.plot.formatDate = function(d, fmt, monthNames) { - var leftPad = function(n) { - n = "" + n; - return n.length == 1 ? "0" + n : n; - }; - - var r = []; - var escape = false, padNext = false; - var hours = d.getUTCHours(); - var isAM = hours < 12; - if (monthNames == null) - monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; - - if (fmt.search(/%p|%P/) != -1) { - if (hours > 12) { - hours = hours - 12; - } else if (hours == 0) { - hours = 12; - } - } - for (var i = 0; i < fmt.length; ++i) { - var c = fmt.charAt(i); - - if (escape) { - switch (c) { - case 'h': c = "" + hours; break; - case 'H': c = leftPad(hours); break; - case 'M': c = leftPad(d.getUTCMinutes()); break; - case 'S': c = leftPad(d.getUTCSeconds()); break; - case 'd': c = "" + d.getUTCDate(); break; - case 'm': c = "" + (d.getUTCMonth() + 1); break; - case 'y': c = "" + d.getUTCFullYear(); break; - case 'b': c = "" + monthNames[d.getUTCMonth()]; break; - case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break; - case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break; - case '0': c = ""; padNext = true; break; - } - if (c && padNext) { - c = leftPad(c); - padNext = false; - } - r.push(c); - if (!padNext) - escape = false; - } - else { - if (c == "%") - escape = true; - else - r.push(c); - } - } - return r.join(""); - }; - - // round to nearby lower multiple of base - function floorInBase(n, base) { - return base * Math.floor(n / base); - } - -})(jQuery); diff --git a/frontend/src/vendor/jquery.flot/jquery.flot.min.js b/frontend/src/vendor/jquery.flot/jquery.flot.min.js deleted file mode 100644 index 4467fc5d8cd..00000000000 --- a/frontend/src/vendor/jquery.flot/jquery.flot.min.js +++ /dev/null @@ -1,6 +0,0 @@ -/* Javascript plotting library for jQuery, v. 0.7. - * - * Released under the MIT license by IOLA, December 2007. - * - */ -(function(b){b.color={};b.color.make=function(d,e,g,f){var c={};c.r=d||0;c.g=e||0;c.b=g||0;c.a=f!=null?f:1;c.add=function(h,j){for(var k=0;k=1){return"rgb("+[c.r,c.g,c.b].join(",")+")"}else{return"rgba("+[c.r,c.g,c.b,c.a].join(",")+")"}};c.normalize=function(){function h(k,j,l){return jl?l:j)}c.r=h(0,parseInt(c.r),255);c.g=h(0,parseInt(c.g),255);c.b=h(0,parseInt(c.b),255);c.a=h(0,c.a,1);return c};c.clone=function(){return b.color.make(c.r,c.b,c.g,c.a)};return c.normalize()};b.color.extract=function(d,e){var c;do{c=d.css(e).toLowerCase();if(c!=""&&c!="transparent"){break}d=d.parent()}while(!b.nodeName(d.get(0),"body"));if(c=="rgba(0, 0, 0, 0)"){c="transparent"}return b.color.parse(c)};b.color.parse=function(c){var d,f=b.color.make;if(d=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(c)){return f(parseInt(d[1],10),parseInt(d[2],10),parseInt(d[3],10))}if(d=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(c)){return f(parseInt(d[1],10),parseInt(d[2],10),parseInt(d[3],10),parseFloat(d[4]))}if(d=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(c)){return f(parseFloat(d[1])*2.55,parseFloat(d[2])*2.55,parseFloat(d[3])*2.55)}if(d=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(c)){return f(parseFloat(d[1])*2.55,parseFloat(d[2])*2.55,parseFloat(d[3])*2.55,parseFloat(d[4]))}if(d=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(c)){return f(parseInt(d[1],16),parseInt(d[2],16),parseInt(d[3],16))}if(d=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(c)){return f(parseInt(d[1]+d[1],16),parseInt(d[2]+d[2],16),parseInt(d[3]+d[3],16))}var e=b.trim(c).toLowerCase();if(e=="transparent"){return f(255,255,255,0)}else{d=a[e]||[0,0,0];return f(d[0],d[1],d[2])}};var a={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery);(function(c){function b(av,ai,J,af){var Q=[],O={colors:["#edc240","#afd8f8","#cb4b4b","#4da74d","#9440ed"],legend:{show:true,noColumns:1,labelFormatter:null,labelBoxBorderColor:"#ccc",container:null,position:"ne",margin:5,backgroundColor:null,backgroundOpacity:0.85},xaxis:{show:null,position:"bottom",mode:null,color:null,tickColor:null,transform:null,inverseTransform:null,min:null,max:null,autoscaleMargin:null,ticks:null,tickFormatter:null,labelWidth:null,labelHeight:null,reserveSpace:null,tickLength:null,alignTicksWithAxis:null,tickDecimals:null,tickSize:null,minTickSize:null,monthNames:null,timeformat:null,twelveHourClock:false},yaxis:{autoscaleMargin:0.02,position:"left"},xaxes:[],yaxes:[],series:{points:{show:false,radius:3,lineWidth:2,fill:true,fillColor:"#ffffff",symbol:"circle"},lines:{lineWidth:2,fill:false,fillColor:null,steps:false},bars:{show:false,lineWidth:2,barWidth:1,fill:true,fillColor:null,align:"left",horizontal:false},shadowSize:3},grid:{show:true,aboveData:false,color:"#545454",backgroundColor:null,borderColor:null,tickColor:null,labelMargin:5,axisMargin:8,borderWidth:2,minBorderMargin:null,markings:null,markingsColor:"#f4f4f4",markingsLineWidth:2,clickable:false,hoverable:false,autoHighlight:true,mouseActiveRadius:10},hooks:{}},az=null,ad=null,y=null,H=null,A=null,p=[],aw=[],q={left:0,right:0,top:0,bottom:0},G=0,I=0,h=0,w=0,ak={processOptions:[],processRawData:[],processDatapoints:[],drawSeries:[],draw:[],bindEvents:[],drawOverlay:[],shutdown:[]},aq=this;aq.setData=aj;aq.setupGrid=t;aq.draw=W;aq.getPlaceholder=function(){return av};aq.getCanvas=function(){return az};aq.getPlotOffset=function(){return q};aq.width=function(){return h};aq.height=function(){return w};aq.offset=function(){var aB=y.offset();aB.left+=q.left;aB.top+=q.top;return aB};aq.getData=function(){return Q};aq.getAxes=function(){var aC={},aB;c.each(p.concat(aw),function(aD,aE){if(aE){aC[aE.direction+(aE.n!=1?aE.n:"")+"axis"]=aE}});return aC};aq.getXAxes=function(){return p};aq.getYAxes=function(){return aw};aq.c2p=C;aq.p2c=ar;aq.getOptions=function(){return O};aq.highlight=x;aq.unhighlight=T;aq.triggerRedrawOverlay=f;aq.pointOffset=function(aB){return{left:parseInt(p[aA(aB,"x")-1].p2c(+aB.x)+q.left),top:parseInt(aw[aA(aB,"y")-1].p2c(+aB.y)+q.top)}};aq.shutdown=ag;aq.resize=function(){B();g(az);g(ad)};aq.hooks=ak;F(aq);Z(J);X();aj(ai);t();W();ah();function an(aD,aB){aB=[aq].concat(aB);for(var aC=0;aC=O.colors.length){aG=0;++aF}}var aH=0,aN;for(aG=0;aGa3.datamax&&a1!=aB){a3.datamax=a1}}c.each(m(),function(a1,a2){a2.datamin=aO;a2.datamax=aI;a2.used=false});for(aU=0;aU0&&aT[aR-aP]!=null&&aT[aR-aP]!=aT[aR]&&aT[aR-aP+1]!=aT[aR+1]){for(aN=0;aNaM){aM=a0}}if(aX.y){if(a0aV){aV=a0}}}}if(aJ.bars.show){var aY=aJ.bars.align=="left"?0:-aJ.bars.barWidth/2;if(aJ.bars.horizontal){aQ+=aY;aV+=aY+aJ.bars.barWidth}else{aK+=aY;aM+=aY+aJ.bars.barWidth}}aF(aJ.xaxis,aK,aM);aF(aJ.yaxis,aQ,aV)}c.each(m(),function(a1,a2){if(a2.datamin==aO){a2.datamin=null}if(a2.datamax==aI){a2.datamax=null}})}function j(aB,aC){var aD=document.createElement("canvas");aD.className=aC;aD.width=G;aD.height=I;if(!aB){c(aD).css({position:"absolute",left:0,top:0})}c(aD).appendTo(av);if(!aD.getContext){aD=window.G_vmlCanvasManager.initElement(aD)}aD.getContext("2d").save();return aD}function B(){G=av.width();I=av.height();if(G<=0||I<=0){throw"Invalid dimensions for plot, width = "+G+", height = "+I}}function g(aC){if(aC.width!=G){aC.width=G}if(aC.height!=I){aC.height=I}var aB=aC.getContext("2d");aB.restore();aB.save()}function X(){var aC,aB=av.children("canvas.base"),aD=av.children("canvas.overlay");if(aB.length==0||aD==0){av.html("");av.css({padding:0});if(av.css("position")=="static"){av.css("position","relative")}B();az=j(true,"base");ad=j(false,"overlay");aC=false}else{az=aB.get(0);ad=aD.get(0);aC=true}H=az.getContext("2d");A=ad.getContext("2d");y=c([ad,az]);if(aC){av.data("plot").shutdown();aq.resize();A.clearRect(0,0,G,I);y.unbind();av.children().not([az,ad]).remove()}av.data("plot",aq)}function ah(){if(O.grid.hoverable){y.mousemove(aa);y.mouseleave(l)}if(O.grid.clickable){y.click(R)}an(ak.bindEvents,[y])}function ag(){if(M){clearTimeout(M)}y.unbind("mousemove",aa);y.unbind("mouseleave",l);y.unbind("click",R);an(ak.shutdown,[y])}function r(aG){function aC(aH){return aH}var aF,aB,aD=aG.options.transform||aC,aE=aG.options.inverseTransform;if(aG.direction=="x"){aF=aG.scale=h/Math.abs(aD(aG.max)-aD(aG.min));aB=Math.min(aD(aG.max),aD(aG.min))}else{aF=aG.scale=w/Math.abs(aD(aG.max)-aD(aG.min));aF=-aF;aB=Math.max(aD(aG.max),aD(aG.min))}if(aD==aC){aG.p2c=function(aH){return(aH-aB)*aF}}else{aG.p2c=function(aH){return(aD(aH)-aB)*aF}}if(!aE){aG.c2p=function(aH){return aB+aH/aF}}else{aG.c2p=function(aH){return aE(aB+aH/aF)}}}function L(aD){var aB=aD.options,aF,aJ=aD.ticks||[],aI=[],aE,aK=aB.labelWidth,aG=aB.labelHeight,aC;function aH(aM,aL){return c('
    '+aM.join("")+"
    ").appendTo(av)}if(aD.direction=="x"){if(aK==null){aK=Math.floor(G/(aJ.length>0?aJ.length:1))}if(aG==null){aI=[];for(aF=0;aF'+aE+"
    ")}}if(aI.length>0){aI.push('
    ');aC=aH(aI,"width:10000px;");aG=aC.height();aC.remove()}}}else{if(aK==null||aG==null){for(aF=0;aF'+aE+"
    ")}}if(aI.length>0){aC=aH(aI,"");if(aK==null){aK=aC.children().width()}if(aG==null){aG=aC.find("div.tickLabel").height()}aC.remove()}}}if(aK==null){aK=0}if(aG==null){aG=0}aD.labelWidth=aK;aD.labelHeight=aG}function au(aD){var aC=aD.labelWidth,aL=aD.labelHeight,aH=aD.options.position,aF=aD.options.tickLength,aG=O.grid.axisMargin,aJ=O.grid.labelMargin,aK=aD.direction=="x"?p:aw,aE;var aB=c.grep(aK,function(aN){return aN&&aN.options.position==aH&&aN.reserveSpace});if(c.inArray(aD,aB)==aB.length-1){aG=0}if(aF==null){aF="full"}var aI=c.grep(aK,function(aN){return aN&&aN.reserveSpace});var aM=c.inArray(aD,aI)==0;if(!aM&&aF=="full"){aF=5}if(!isNaN(+aF)){aJ+=+aF}if(aD.direction=="x"){aL+=aJ;if(aH=="bottom"){q.bottom+=aL+aG;aD.box={top:I-q.bottom,height:aL}}else{aD.box={top:q.top+aG,height:aL};q.top+=aL+aG}}else{aC+=aJ;if(aH=="left"){aD.box={left:q.left+aG,width:aC};q.left+=aC+aG}else{q.right+=aC+aG;aD.box={left:G-q.right,width:aC}}}aD.position=aH;aD.tickLength=aF;aD.box.padding=aJ;aD.innermost=aM}function U(aB){if(aB.direction=="x"){aB.box.left=q.left;aB.box.width=h}else{aB.box.top=q.top;aB.box.height=w}}function t(){var aC,aE=m();c.each(aE,function(aF,aG){aG.show=aG.options.show;if(aG.show==null){aG.show=aG.used}aG.reserveSpace=aG.show||aG.options.reserveSpace;n(aG)});allocatedAxes=c.grep(aE,function(aF){return aF.reserveSpace});q.left=q.right=q.top=q.bottom=0;if(O.grid.show){c.each(allocatedAxes,function(aF,aG){S(aG);P(aG);ap(aG,aG.ticks);L(aG)});for(aC=allocatedAxes.length-1;aC>=0;--aC){au(allocatedAxes[aC])}var aD=O.grid.minBorderMargin;if(aD==null){aD=0;for(aC=0;aC=0){aD=0}}if(aF.max==null){aB+=aH*aG;if(aB>0&&aE.datamax!=null&&aE.datamax<=0){aB=0}}}}aE.min=aD;aE.max=aB}function S(aG){var aM=aG.options;var aH;if(typeof aM.ticks=="number"&&aM.ticks>0){aH=aM.ticks}else{aH=0.3*Math.sqrt(aG.direction=="x"?G:I)}var aT=(aG.max-aG.min)/aH,aO,aB,aN,aR,aS,aQ,aI;if(aM.mode=="time"){var aJ={second:1000,minute:60*1000,hour:60*60*1000,day:24*60*60*1000,month:30*24*60*60*1000,year:365.2425*24*60*60*1000};var aK=[[1,"second"],[2,"second"],[5,"second"],[10,"second"],[30,"second"],[1,"minute"],[2,"minute"],[5,"minute"],[10,"minute"],[30,"minute"],[1,"hour"],[2,"hour"],[4,"hour"],[8,"hour"],[12,"hour"],[1,"day"],[2,"day"],[3,"day"],[0.25,"month"],[0.5,"month"],[1,"month"],[2,"month"],[3,"month"],[6,"month"],[1,"year"]];var aC=0;if(aM.minTickSize!=null){if(typeof aM.tickSize=="number"){aC=aM.tickSize}else{aC=aM.minTickSize[0]*aJ[aM.minTickSize[1]]}}for(var aS=0;aS=aC){break}}aO=aK[aS][0];aN=aK[aS][1];if(aN=="year"){aQ=Math.pow(10,Math.floor(Math.log(aT/aJ.year)/Math.LN10));aI=(aT/aJ.year)/aQ;if(aI<1.5){aO=1}else{if(aI<3){aO=2}else{if(aI<7.5){aO=5}else{aO=10}}}aO*=aQ}aG.tickSize=aM.tickSize||[aO,aN];aB=function(aX){var a2=[],a0=aX.tickSize[0],a3=aX.tickSize[1],a1=new Date(aX.min);var aW=a0*aJ[a3];if(a3=="second"){a1.setUTCSeconds(a(a1.getUTCSeconds(),a0))}if(a3=="minute"){a1.setUTCMinutes(a(a1.getUTCMinutes(),a0))}if(a3=="hour"){a1.setUTCHours(a(a1.getUTCHours(),a0))}if(a3=="month"){a1.setUTCMonth(a(a1.getUTCMonth(),a0))}if(a3=="year"){a1.setUTCFullYear(a(a1.getUTCFullYear(),a0))}a1.setUTCMilliseconds(0);if(aW>=aJ.minute){a1.setUTCSeconds(0)}if(aW>=aJ.hour){a1.setUTCMinutes(0)}if(aW>=aJ.day){a1.setUTCHours(0)}if(aW>=aJ.day*4){a1.setUTCDate(1)}if(aW>=aJ.year){a1.setUTCMonth(0)}var a5=0,a4=Number.NaN,aY;do{aY=a4;a4=a1.getTime();a2.push(a4);if(a3=="month"){if(a0<1){a1.setUTCDate(1);var aV=a1.getTime();a1.setUTCMonth(a1.getUTCMonth()+1);var aZ=a1.getTime();a1.setTime(a4+a5*aJ.hour+(aZ-aV)*a0);a5=a1.getUTCHours();a1.setUTCHours(0)}else{a1.setUTCMonth(a1.getUTCMonth()+a0)}}else{if(a3=="year"){a1.setUTCFullYear(a1.getUTCFullYear()+a0)}else{a1.setTime(a4+aW)}}}while(a4aU){aP=aU}aQ=Math.pow(10,-aP);aI=aT/aQ;if(aI<1.5){aO=1}else{if(aI<3){aO=2;if(aI>2.25&&(aU==null||aP+1<=aU)){aO=2.5;++aP}}else{if(aI<7.5){aO=5}else{aO=10}}}aO*=aQ;if(aM.minTickSize!=null&&aO0){if(aM.min==null){aG.min=Math.min(aG.min,aL[0])}if(aM.max==null&&aL.length>1){aG.max=Math.max(aG.max,aL[aL.length-1])}}aB=function(aX){var aY=[],aV,aW;for(aW=0;aW1&&/\..*0$/.test((aD[1]-aD[0]).toFixed(aE)))){aG.tickDecimals=aE}}}}aG.tickGenerator=aB;if(c.isFunction(aM.tickFormatter)){aG.tickFormatter=function(aV,aW){return""+aM.tickFormatter(aV,aW)}}else{aG.tickFormatter=aR}}function P(aF){var aH=aF.options.ticks,aG=[];if(aH==null||(typeof aH=="number"&&aH>0)){aG=aF.tickGenerator(aF)}else{if(aH){if(c.isFunction(aH)){aG=aH({min:aF.min,max:aF.max})}else{aG=aH}}}var aE,aB;aF.ticks=[];for(aE=0;aE1){aC=aD[1]}}else{aB=+aD}if(aC==null){aC=aF.tickFormatter(aB,aF)}if(!isNaN(aB)){aF.ticks.push({v:aB,label:aC})}}}function ap(aB,aC){if(aB.options.autoscaleMargin&&aC.length>0){if(aB.options.min==null){aB.min=Math.min(aB.min,aC[0].v)}if(aB.options.max==null&&aC.length>1){aB.max=Math.max(aB.max,aC[aC.length-1].v)}}}function W(){H.clearRect(0,0,G,I);var aC=O.grid;if(aC.show&&aC.backgroundColor){N()}if(aC.show&&!aC.aboveData){ac()}for(var aB=0;aBaG){var aC=aH;aH=aG;aG=aC}return{from:aH,to:aG,axis:aE}}function N(){H.save();H.translate(q.left,q.top);H.fillStyle=am(O.grid.backgroundColor,w,0,"rgba(255, 255, 255, 0)");H.fillRect(0,0,h,w);H.restore()}function ac(){var aF;H.save();H.translate(q.left,q.top);var aH=O.grid.markings;if(aH){if(c.isFunction(aH)){var aK=aq.getAxes();aK.xmin=aK.xaxis.min;aK.xmax=aK.xaxis.max;aK.ymin=aK.yaxis.min;aK.ymax=aK.yaxis.max;aH=aH(aK)}for(aF=0;aFaC.axis.max||aI.toaI.axis.max){continue}aC.from=Math.max(aC.from,aC.axis.min);aC.to=Math.min(aC.to,aC.axis.max);aI.from=Math.max(aI.from,aI.axis.min);aI.to=Math.min(aI.to,aI.axis.max);if(aC.from==aC.to&&aI.from==aI.to){continue}aC.from=aC.axis.p2c(aC.from);aC.to=aC.axis.p2c(aC.to);aI.from=aI.axis.p2c(aI.from);aI.to=aI.axis.p2c(aI.to);if(aC.from==aC.to||aI.from==aI.to){H.beginPath();H.strokeStyle=aD.color||O.grid.markingsColor;H.lineWidth=aD.lineWidth||O.grid.markingsLineWidth;H.moveTo(aC.from,aI.from);H.lineTo(aC.to,aI.to);H.stroke()}else{H.fillStyle=aD.color||O.grid.markingsColor;H.fillRect(aC.from,aI.to,aC.to-aC.from,aI.from-aI.to)}}}var aK=m(),aM=O.grid.borderWidth;for(var aE=0;aEaB.max||(aQ=="full"&&aM>0&&(aO==aB.min||aO==aB.max))){continue}if(aB.direction=="x"){aN=aB.p2c(aO);aJ=aQ=="full"?-w:aQ;if(aB.position=="top"){aJ=-aJ}}else{aL=aB.p2c(aO);aP=aQ=="full"?-h:aQ;if(aB.position=="left"){aP=-aP}}if(H.lineWidth==1){if(aB.direction=="x"){aN=Math.floor(aN)+0.5}else{aL=Math.floor(aL)+0.5}}H.moveTo(aN,aL);H.lineTo(aN+aP,aL+aJ)}H.stroke()}if(aM){H.lineWidth=aM;H.strokeStyle=O.grid.borderColor;H.strokeRect(-aM/2,-aM/2,h+aM,w+aM)}H.restore()}function k(){av.find(".tickLabels").remove();var aG=['
    '];var aJ=m();for(var aD=0;aD');for(var aE=0;aEaC.max){continue}var aK={},aI;if(aC.direction=="x"){aI="center";aK.left=Math.round(q.left+aC.p2c(aH.v)-aC.labelWidth/2);if(aC.position=="bottom"){aK.top=aF.top+aF.padding}else{aK.bottom=I-(aF.top+aF.height-aF.padding)}}else{aK.top=Math.round(q.top+aC.p2c(aH.v)-aC.labelHeight/2);if(aC.position=="left"){aK.right=G-(aF.left+aF.width-aF.padding);aI="right"}else{aK.left=aF.left+aF.padding;aI="left"}}aK.width=aC.labelWidth;var aB=["position:absolute","text-align:"+aI];for(var aL in aK){aB.push(aL+":"+aK[aL]+"px")}aG.push('
    '+aH.label+"
    ")}aG.push("
    ")}aG.push("
    ");av.append(aG.join(""))}function d(aB){if(aB.lines.show){at(aB)}if(aB.bars.show){e(aB)}if(aB.points.show){ao(aB)}}function at(aE){function aD(aP,aQ,aI,aU,aT){var aV=aP.points,aJ=aP.pointsize,aN=null,aM=null;H.beginPath();for(var aO=aJ;aO=aR&&aS>aT.max){if(aR>aT.max){continue}aL=(aT.max-aS)/(aR-aS)*(aK-aL)+aL;aS=aT.max}else{if(aR>=aS&&aR>aT.max){if(aS>aT.max){continue}aK=(aT.max-aS)/(aR-aS)*(aK-aL)+aL;aR=aT.max}}if(aL<=aK&&aL=aK&&aL>aU.max){if(aK>aU.max){continue}aS=(aU.max-aL)/(aK-aL)*(aR-aS)+aS;aL=aU.max}else{if(aK>=aL&&aK>aU.max){if(aL>aU.max){continue}aR=(aU.max-aL)/(aK-aL)*(aR-aS)+aS;aK=aU.max}}if(aL!=aN||aS!=aM){H.moveTo(aU.p2c(aL)+aQ,aT.p2c(aS)+aI)}aN=aK;aM=aR;H.lineTo(aU.p2c(aK)+aQ,aT.p2c(aR)+aI)}H.stroke()}function aF(aI,aQ,aP){var aW=aI.points,aV=aI.pointsize,aN=Math.min(Math.max(0,aP.min),aP.max),aX=0,aU,aT=false,aM=1,aL=0,aR=0;while(true){if(aV>0&&aX>aW.length+aV){break}aX+=aV;var aZ=aW[aX-aV],aK=aW[aX-aV+aM],aY=aW[aX],aJ=aW[aX+aM];if(aT){if(aV>0&&aZ!=null&&aY==null){aR=aX;aV=-aV;aM=2;continue}if(aV<0&&aX==aL+aV){H.fill();aT=false;aV=-aV;aM=1;aX=aL=aR+aV;continue}}if(aZ==null||aY==null){continue}if(aZ<=aY&&aZ=aY&&aZ>aQ.max){if(aY>aQ.max){continue}aK=(aQ.max-aZ)/(aY-aZ)*(aJ-aK)+aK;aZ=aQ.max}else{if(aY>=aZ&&aY>aQ.max){if(aZ>aQ.max){continue}aJ=(aQ.max-aZ)/(aY-aZ)*(aJ-aK)+aK;aY=aQ.max}}if(!aT){H.beginPath();H.moveTo(aQ.p2c(aZ),aP.p2c(aN));aT=true}if(aK>=aP.max&&aJ>=aP.max){H.lineTo(aQ.p2c(aZ),aP.p2c(aP.max));H.lineTo(aQ.p2c(aY),aP.p2c(aP.max));continue}else{if(aK<=aP.min&&aJ<=aP.min){H.lineTo(aQ.p2c(aZ),aP.p2c(aP.min));H.lineTo(aQ.p2c(aY),aP.p2c(aP.min));continue}}var aO=aZ,aS=aY;if(aK<=aJ&&aK=aP.min){aZ=(aP.min-aK)/(aJ-aK)*(aY-aZ)+aZ;aK=aP.min}else{if(aJ<=aK&&aJ=aP.min){aY=(aP.min-aK)/(aJ-aK)*(aY-aZ)+aZ;aJ=aP.min}}if(aK>=aJ&&aK>aP.max&&aJ<=aP.max){aZ=(aP.max-aK)/(aJ-aK)*(aY-aZ)+aZ;aK=aP.max}else{if(aJ>=aK&&aJ>aP.max&&aK<=aP.max){aY=(aP.max-aK)/(aJ-aK)*(aY-aZ)+aZ;aJ=aP.max}}if(aZ!=aO){H.lineTo(aQ.p2c(aO),aP.p2c(aK))}H.lineTo(aQ.p2c(aZ),aP.p2c(aK));H.lineTo(aQ.p2c(aY),aP.p2c(aJ));if(aY!=aS){H.lineTo(aQ.p2c(aY),aP.p2c(aJ));H.lineTo(aQ.p2c(aS),aP.p2c(aJ))}}}H.save();H.translate(q.left,q.top);H.lineJoin="round";var aG=aE.lines.lineWidth,aB=aE.shadowSize;if(aG>0&&aB>0){H.lineWidth=aB;H.strokeStyle="rgba(0,0,0,0.1)";var aH=Math.PI/18;aD(aE.datapoints,Math.sin(aH)*(aG/2+aB/2),Math.cos(aH)*(aG/2+aB/2),aE.xaxis,aE.yaxis);H.lineWidth=aB/2;aD(aE.datapoints,Math.sin(aH)*(aG/2+aB/4),Math.cos(aH)*(aG/2+aB/4),aE.xaxis,aE.yaxis)}H.lineWidth=aG;H.strokeStyle=aE.color;var aC=ae(aE.lines,aE.color,0,w);if(aC){H.fillStyle=aC;aF(aE.datapoints,aE.xaxis,aE.yaxis)}if(aG>0){aD(aE.datapoints,0,0,aE.xaxis,aE.yaxis)}H.restore()}function ao(aE){function aH(aN,aM,aU,aK,aS,aT,aQ,aJ){var aR=aN.points,aI=aN.pointsize;for(var aL=0;aLaT.max||aOaQ.max){continue}H.beginPath();aP=aT.p2c(aP);aO=aQ.p2c(aO)+aK;if(aJ=="circle"){H.arc(aP,aO,aM,0,aS?Math.PI:Math.PI*2,false)}else{aJ(H,aP,aO,aM,aS)}H.closePath();if(aU){H.fillStyle=aU;H.fill()}H.stroke()}}H.save();H.translate(q.left,q.top);var aG=aE.points.lineWidth,aC=aE.shadowSize,aB=aE.points.radius,aF=aE.points.symbol;if(aG>0&&aC>0){var aD=aC/2;H.lineWidth=aD;H.strokeStyle="rgba(0,0,0,0.1)";aH(aE.datapoints,aB,null,aD+aD/2,true,aE.xaxis,aE.yaxis,aF);H.strokeStyle="rgba(0,0,0,0.2)";aH(aE.datapoints,aB,null,aD/2,true,aE.xaxis,aE.yaxis,aF)}H.lineWidth=aG;H.strokeStyle=aE.color;aH(aE.datapoints,aB,ae(aE.points,aE.color),0,false,aE.xaxis,aE.yaxis,aF);H.restore()}function E(aN,aM,aV,aI,aQ,aF,aD,aL,aK,aU,aR,aC){var aE,aT,aJ,aP,aG,aB,aO,aH,aS;if(aR){aH=aB=aO=true;aG=false;aE=aV;aT=aN;aP=aM+aI;aJ=aM+aQ;if(aTaL.max||aPaK.max){return}if(aEaL.max){aT=aL.max;aB=false}if(aJaK.max){aP=aK.max;aO=false}aE=aL.p2c(aE);aJ=aK.p2c(aJ);aT=aL.p2c(aT);aP=aK.p2c(aP);if(aD){aU.beginPath();aU.moveTo(aE,aJ);aU.lineTo(aE,aP);aU.lineTo(aT,aP);aU.lineTo(aT,aJ);aU.fillStyle=aD(aJ,aP);aU.fill()}if(aC>0&&(aG||aB||aO||aH)){aU.beginPath();aU.moveTo(aE,aJ+aF);if(aG){aU.lineTo(aE,aP+aF)}else{aU.moveTo(aE,aP+aF)}if(aO){aU.lineTo(aT,aP+aF)}else{aU.moveTo(aT,aP+aF)}if(aB){aU.lineTo(aT,aJ+aF)}else{aU.moveTo(aT,aJ+aF)}if(aH){aU.lineTo(aE,aJ+aF)}else{aU.moveTo(aE,aJ+aF)}aU.stroke()}}function e(aD){function aC(aJ,aI,aL,aG,aK,aN,aM){var aO=aJ.points,aF=aJ.pointsize;for(var aH=0;aH")}aH.push("");aF=true}if(aN){aJ=aN(aJ,aM)}aH.push('
    '+aJ+"")}if(aF){aH.push("")}if(aH.length==0){return}var aL=''+aH.join("")+"
    ";if(O.legend.container!=null){c(O.legend.container).html(aL)}else{var aI="",aC=O.legend.position,aD=O.legend.margin;if(aD[0]==null){aD=[aD,aD]}if(aC.charAt(0)=="n"){aI+="top:"+(aD[1]+q.top)+"px;"}else{if(aC.charAt(0)=="s"){aI+="bottom:"+(aD[1]+q.bottom)+"px;"}}if(aC.charAt(1)=="e"){aI+="right:"+(aD[0]+q.right)+"px;"}else{if(aC.charAt(1)=="w"){aI+="left:"+(aD[0]+q.left)+"px;"}}var aK=c('
    '+aL.replace('style="','style="position:absolute;'+aI+";")+"
    ").appendTo(av);if(O.legend.backgroundOpacity!=0){var aG=O.legend.backgroundColor;if(aG==null){aG=O.grid.backgroundColor;if(aG&&typeof aG=="string"){aG=c.color.parse(aG)}else{aG=c.color.extract(aK,"background-color")}aG.a=1;aG=aG.toString()}var aB=aK.children();c('
    ').prependTo(aK).css("opacity",O.legend.backgroundOpacity)}}}var ab=[],M=null;function K(aI,aG,aD){var aO=O.grid.mouseActiveRadius,a0=aO*aO+1,aY=null,aR=false,aW,aU;for(aW=Q.length-1;aW>=0;--aW){if(!aD(Q[aW])){continue}var aP=Q[aW],aH=aP.xaxis,aF=aP.yaxis,aV=aP.datapoints.points,aT=aP.datapoints.pointsize,aQ=aH.c2p(aI),aN=aF.c2p(aG),aC=aO/aH.scale,aB=aO/aF.scale;if(aH.options.inverseTransform){aC=Number.MAX_VALUE}if(aF.options.inverseTransform){aB=Number.MAX_VALUE}if(aP.lines.show||aP.points.show){for(aU=0;aUaC||aK-aQ<-aC||aJ-aN>aB||aJ-aN<-aB){continue}var aM=Math.abs(aH.p2c(aK)-aI),aL=Math.abs(aF.p2c(aJ)-aG),aS=aM*aM+aL*aL;if(aS=Math.min(aZ,aK)&&aN>=aJ+aE&&aN<=aJ+aX):(aQ>=aK+aE&&aQ<=aK+aX&&aN>=Math.min(aZ,aJ)&&aN<=Math.max(aZ,aJ))){aY=[aW,aU/aT]}}}}if(aY){aW=aY[0];aU=aY[1];aT=Q[aW].datapoints.pointsize;return{datapoint:Q[aW].datapoints.points.slice(aU*aT,(aU+1)*aT),dataIndex:aU,series:Q[aW],seriesIndex:aW}}return null}function aa(aB){if(O.grid.hoverable){u("plothover",aB,function(aC){return aC.hoverable!=false})}}function l(aB){if(O.grid.hoverable){u("plothover",aB,function(aC){return false})}}function R(aB){u("plotclick",aB,function(aC){return aC.clickable!=false})}function u(aC,aB,aD){var aE=y.offset(),aH=aB.pageX-aE.left-q.left,aF=aB.pageY-aE.top-q.top,aJ=C({left:aH,top:aF});aJ.pageX=aB.pageX;aJ.pageY=aB.pageY;var aK=K(aH,aF,aD);if(aK){aK.pageX=parseInt(aK.series.xaxis.p2c(aK.datapoint[0])+aE.left+q.left);aK.pageY=parseInt(aK.series.yaxis.p2c(aK.datapoint[1])+aE.top+q.top)}if(O.grid.autoHighlight){for(var aG=0;aGaH.max||aIaG.max){return}var aF=aE.points.radius+aE.points.lineWidth/2;A.lineWidth=aF;A.strokeStyle=c.color.parse(aE.color).scale("a",0.5).toString();var aB=1.5*aF,aC=aH.p2c(aC),aI=aG.p2c(aI);A.beginPath();if(aE.points.symbol=="circle"){A.arc(aC,aI,aB,0,2*Math.PI,false)}else{aE.points.symbol(A,aC,aI,aB,false)}A.closePath();A.stroke()}function v(aE,aB){A.lineWidth=aE.bars.lineWidth;A.strokeStyle=c.color.parse(aE.color).scale("a",0.5).toString();var aD=c.color.parse(aE.color).scale("a",0.5).toString();var aC=aE.bars.align=="left"?0:-aE.bars.barWidth/2;E(aB[0],aB[1],aB[2]||0,aC,aC+aE.bars.barWidth,0,function(){return aD},aE.xaxis,aE.yaxis,A,aE.bars.horizontal,aE.bars.lineWidth)}function am(aJ,aB,aH,aC){if(typeof aJ=="string"){return aJ}else{var aI=H.createLinearGradient(0,aH,0,aB);for(var aE=0,aD=aJ.colors.length;aE12){n=n-12}else{if(n==0){n=12}}}for(var g=0;g0&&L.which!=M.which)||E(L.target).is(M.not)){return }}switch(L.type){case"mousedown":E.extend(M,E(K).offset(),{elem:K,target:L.target,pageX:L.pageX,pageY:L.pageY});A.add(document,"mousemove mouseup",H,M);G(K,false);F.dragging=null;return false;case !F.dragging&&"mousemove":if(I(L.pageX-M.pageX)+I(L.pageY-M.pageY) max) { - // make sure min < max - var tmp = min; - min = max; - max = tmp; - } - - var range = max - min; - if (zr && - ((zr[0] != null && range < zr[0]) || - (zr[1] != null && range > zr[1]))) - return; - - opts.min = min; - opts.max = max; - }); - - plot.setupGrid(); - plot.draw(); - - if (!args.preventEvent) - plot.getPlaceholder().trigger("plotzoom", [ plot ]); - } - - plot.pan = function (args) { - var delta = { - x: +args.left, - y: +args.top - }; - - if (isNaN(delta.x)) - delta.x = 0; - if (isNaN(delta.y)) - delta.y = 0; - - $.each(plot.getAxes(), function (_, axis) { - var opts = axis.options, - min, max, d = delta[axis.direction]; - - min = axis.c2p(axis.p2c(axis.min) + d), - max = axis.c2p(axis.p2c(axis.max) + d); - - var pr = opts.panRange; - if (pr === false) // no panning on this axis - return; - - if (pr) { - // check whether we hit the wall - if (pr[0] != null && pr[0] > min) { - d = pr[0] - min; - min += d; - max += d; - } - - if (pr[1] != null && pr[1] < max) { - d = pr[1] - max; - min += d; - max += d; - } - } - - opts.min = min; - opts.max = max; - }); - - plot.setupGrid(); - plot.draw(); - - if (!args.preventEvent) - plot.getPlaceholder().trigger("plotpan", [ plot ]); - } - - function shutdown(plot, eventHolder) { - eventHolder.unbind(plot.getOptions().zoom.trigger, onZoomClick); - eventHolder.unbind("mousewheel", onMouseWheel); - eventHolder.unbind("dragstart", onDragStart); - eventHolder.unbind("drag", onDrag); - eventHolder.unbind("dragend", onDragEnd); - if (panTimeout) - clearTimeout(panTimeout); - } - - plot.hooks.bindEvents.push(bindEvents); - plot.hooks.shutdown.push(shutdown); - } - - $.plot.plugins.push({ - init: init, - options: options, - name: 'navigate', - version: '1.3' - }); -})(jQuery); diff --git a/frontend/src/vendor/jquery.flot/jquery.flot.navigate.min.js b/frontend/src/vendor/jquery.flot/jquery.flot.navigate.min.js deleted file mode 100644 index ecf63c93ba5..00000000000 --- a/frontend/src/vendor/jquery.flot/jquery.flot.navigate.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(i){i.fn.drag=function(j,k,l){if(k){this.bind("dragstart",j)}if(l){this.bind("dragend",l)}return !j?this.trigger("drag"):this.bind("drag",k?k:j)};var d=i.event,c=d.special,h=c.drag={not:":input",distance:0,which:1,dragging:false,setup:function(j){j=i.extend({distance:h.distance,which:h.which,not:h.not},j||{});j.distance=e(j.distance);d.add(this,"mousedown",f,j);if(this.attachEvent){this.attachEvent("ondragstart",a)}},teardown:function(){d.remove(this,"mousedown",f);if(this===h.dragging){h.dragging=h.proxy=false}g(this,true);if(this.detachEvent){this.detachEvent("ondragstart",a)}}};c.dragstart=c.dragend={setup:function(){},teardown:function(){}};function f(j){var k=this,l,m=j.data||{};if(m.elem){k=j.dragTarget=m.elem;j.dragProxy=h.proxy||k;j.cursorOffsetX=m.pageX-m.left;j.cursorOffsetY=m.pageY-m.top;j.offsetX=j.pageX-j.cursorOffsetX;j.offsetY=j.pageY-j.cursorOffsetY}else{if(h.dragging||(m.which>0&&j.which!=m.which)||i(j.target).is(m.not)){return}}switch(j.type){case"mousedown":i.extend(m,i(k).offset(),{elem:k,target:j.target,pageX:j.pageX,pageY:j.pageY});d.add(document,"mousemove mouseup",f,m);g(k,false);h.dragging=null;return false;case !h.dragging&&"mousemove":if(e(j.pageX-m.pageX)+e(j.pageY-m.pageY)w){var A=B;B=w;w=A}var y=w-B;if(E&&((E[0]!=null&&yE[1]))){return}D.min=B;D.max=w});o.setupGrid();o.draw();if(!q.preventEvent){o.getPlaceholder().trigger("plotzoom",[o])}};o.pan=function(p){var q={x:+p.left,y:+p.top};if(isNaN(q.x)){q.x=0}if(isNaN(q.y)){q.y=0}b.each(o.getAxes(),function(s,u){var v=u.options,t,r,w=q[u.direction];t=u.c2p(u.p2c(u.min)+w),r=u.c2p(u.p2c(u.max)+w);var x=v.panRange;if(x===false){return}if(x){if(x[0]!=null&&x[0]>t){w=x[0]-t;t+=w;r+=w}if(x[1]!=null&&x[1]1) - options.series.pie.tilt=1; - if (options.series.pie.tilt<0) - options.series.pie.tilt=0; - - // add processData hook to do transformations on the data - plot.hooks.processDatapoints.push(processDatapoints); - plot.hooks.drawOverlay.push(drawOverlay); - - // add draw hook - plot.hooks.draw.push(draw); - } - } - - // bind hoverable events - function bindEvents(plot, eventHolder) - { - var options = plot.getOptions(); - - if (options.series.pie.show && options.grid.hoverable) - eventHolder.unbind('mousemove').mousemove(onMouseMove); - - if (options.series.pie.show && options.grid.clickable) - eventHolder.unbind('click').click(onClick); - } - - - // debugging function that prints out an object - function alertObject(obj) - { - var msg = ''; - function traverse(obj, depth) - { - if (!depth) - depth = 0; - for (var i = 0; i < obj.length; ++i) - { - for (var j=0; jcanvas.width-maxRadius) - centerLeft = canvas.width-maxRadius; - } - - function fixData(data) - { - for (var i = 0; i < data.length; ++i) - { - if (typeof(data[i].data)=='number') - data[i].data = [[1,data[i].data]]; - else if (typeof(data[i].data)=='undefined' || typeof(data[i].data[0])=='undefined') - { - if (typeof(data[i].data)!='undefined' && typeof(data[i].data.label)!='undefined') - data[i].label = data[i].data.label; // fix weirdness coming from flot - data[i].data = [[1,0]]; - - } - } - return data; - } - - function combine(data) - { - data = fixData(data); - calcTotal(data); - var combined = 0; - var numCombined = 0; - var color = options.series.pie.combine.color; - - var newdata = []; - for (var i = 0; i < data.length; ++i) - { - // make sure its a number - data[i].data[0][1] = parseFloat(data[i].data[0][1]); - if (!data[i].data[0][1]) - data[i].data[0][1] = 0; - - if (data[i].data[0][1]/total<=options.series.pie.combine.threshold) - { - combined += data[i].data[0][1]; - numCombined++; - if (!color) - color = data[i].color; - } - else - { - newdata.push({ - data: [[1,data[i].data[0][1]]], - color: data[i].color, - label: data[i].label, - angle: (data[i].data[0][1]*(Math.PI*2))/total, - percent: (data[i].data[0][1]/total*100) - }); - } - } - if (numCombined>0) - newdata.push({ - data: [[1,combined]], - color: color, - label: options.series.pie.combine.label, - angle: (combined*(Math.PI*2))/total, - percent: (combined/total*100) - }); - return newdata; - } - - function draw(plot, newCtx) - { - if (!target) return; // if no series were passed - ctx = newCtx; - - setupPie(); - var slices = plot.getData(); - - var attempts = 0; - while (redraw && attempts0) - maxRadius *= shrink; - attempts += 1; - clear(); - if (options.series.pie.tilt<=0.8) - drawShadow(); - drawPie(); - } - if (attempts >= redrawAttempts) { - clear(); - target.prepend('
    Could not draw pie with labels contained inside canvas
    '); - } - - if ( plot.setSeries && plot.insertLegend ) - { - plot.setSeries(slices); - plot.insertLegend(); - } - - // we're actually done at this point, just defining internal functions at this point - - function clear() - { - ctx.clearRect(0,0,canvas.width,canvas.height); - target.children().filter('.pieLabel, .pieLabelBackground').remove(); - } - - function drawShadow() - { - var shadowLeft = 5; - var shadowTop = 15; - var edge = 10; - var alpha = 0.02; - - // set radius - if (options.series.pie.radius>1) - var radius = options.series.pie.radius; - else - var radius = maxRadius * options.series.pie.radius; - - if (radius>=(canvas.width/2)-shadowLeft || radius*options.series.pie.tilt>=(canvas.height/2)-shadowTop || radius<=edge) - return; // shadow would be outside canvas, so don't draw it - - ctx.save(); - ctx.translate(shadowLeft,shadowTop); - ctx.globalAlpha = alpha; - ctx.fillStyle = '#000'; - - // center and rotate to starting position - ctx.translate(centerLeft,centerTop); - ctx.scale(1, options.series.pie.tilt); - - //radius -= edge; - for (var i=1; i<=edge; i++) - { - ctx.beginPath(); - ctx.arc(0,0,radius,0,Math.PI*2,false); - ctx.fill(); - radius -= i; - } - - ctx.restore(); - } - - function drawPie() - { - startAngle = Math.PI*options.series.pie.startAngle; - - // set radius - if (options.series.pie.radius>1) - var radius = options.series.pie.radius; - else - var radius = maxRadius * options.series.pie.radius; - - // center and rotate to starting position - ctx.save(); - ctx.translate(centerLeft,centerTop); - ctx.scale(1, options.series.pie.tilt); - //ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera - - // draw slices - ctx.save(); - var currentAngle = startAngle; - for (var i = 0; i < slices.length; ++i) - { - slices[i].startAngle = currentAngle; - drawSlice(slices[i].angle, slices[i].color, true); - } - ctx.restore(); - - // draw slice outlines - ctx.save(); - ctx.lineWidth = options.series.pie.stroke.width; - currentAngle = startAngle; - for (var i = 0; i < slices.length; ++i) - drawSlice(slices[i].angle, options.series.pie.stroke.color, false); - ctx.restore(); - - // draw donut hole - drawDonutHole(ctx); - - // draw labels - if (options.series.pie.label.show) - drawLabels(); - - // restore to original state - ctx.restore(); - - function drawSlice(angle, color, fill) - { - if (angle<=0) - return; - - if (fill) - ctx.fillStyle = color; - else - { - ctx.strokeStyle = color; - ctx.lineJoin = 'round'; - } - - ctx.beginPath(); - if (Math.abs(angle - Math.PI*2) > 0.000000001) - ctx.moveTo(0,0); // Center of the pie - //ctx.arc(0,0,radius,0,angle,false); // This doesn't work properly in Opera - ctx.arc(0,0,radius,currentAngle,currentAngle+angle,false); - ctx.closePath(); - //ctx.rotate(angle); // This doesn't work properly in Opera - currentAngle += angle; - - if (fill) - ctx.fill(); - else - ctx.stroke(); - } - - function drawLabels() - { - var currentAngle = startAngle; - - // set radius - if (options.series.pie.label.radius>1) - var radius = options.series.pie.label.radius; - else - var radius = maxRadius * options.series.pie.label.radius; - - for (var i = 0; i < slices.length; ++i) - { - if (slices[i].percent >= options.series.pie.label.threshold*100) - drawLabel(slices[i], currentAngle, i); - currentAngle += slices[i].angle; - } - - function drawLabel(slice, startAngle, index) - { - if (slice.data[0][1]==0) - return; - - // format label text - var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter; - if (lf) - text = lf(slice.label, slice); - else - text = slice.label; - if (plf) - text = plf(text, slice); - - var halfAngle = ((startAngle+slice.angle) + startAngle)/2; - var x = centerLeft + Math.round(Math.cos(halfAngle) * radius); - var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt; - - var html = '' + text + ""; - target.append(html); - var label = target.children('#pieLabel'+index); - var labelTop = (y - label.height()/2); - var labelLeft = (x - label.width()/2); - label.css('top', labelTop); - label.css('left', labelLeft); - - // check to make sure that the label is not outside the canvas - if (0-labelTop>0 || 0-labelLeft>0 || canvas.height-(labelTop+label.height())<0 || canvas.width-(labelLeft+label.width())<0) - redraw = true; - - if (options.series.pie.label.background.opacity != 0) { - // put in the transparent background separately to avoid blended labels and label boxes - var c = options.series.pie.label.background.color; - if (c == null) { - c = slice.color; - } - var pos = 'top:'+labelTop+'px;left:'+labelLeft+'px;'; - $('
    ').insertBefore(label).css('opacity', options.series.pie.label.background.opacity); - } - } // end individual label function - } // end drawLabels function - } // end drawPie function - } // end draw function - - // Placed here because it needs to be accessed from multiple locations - function drawDonutHole(layer) - { - // draw donut hole - if(options.series.pie.innerRadius > 0) - { - // subtract the center - layer.save(); - innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius; - layer.globalCompositeOperation = 'destination-out'; // this does not work with excanvas, but it will fall back to using the stroke color - layer.beginPath(); - layer.fillStyle = options.series.pie.stroke.color; - layer.arc(0,0,innerRadius,0,Math.PI*2,false); - layer.fill(); - layer.closePath(); - layer.restore(); - - // add inner stroke - layer.save(); - layer.beginPath(); - layer.strokeStyle = options.series.pie.stroke.color; - layer.arc(0,0,innerRadius,0,Math.PI*2,false); - layer.stroke(); - layer.closePath(); - layer.restore(); - // TODO: add extra shadow inside hole (with a mask) if the pie is tilted. - } - } - - //-- Additional Interactive related functions -- - - function isPointInPoly(poly, pt) - { - for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i) - ((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || (poly[j][1] <= pt[1] && pt[1]< poly[i][1])) - && (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0]) - && (c = !c); - return c; - } - - function findNearbySlice(mouseX, mouseY) - { - var slices = plot.getData(), - options = plot.getOptions(), - radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; - - for (var i = 0; i < slices.length; ++i) - { - var s = slices[i]; - - if(s.pie.show) - { - ctx.save(); - ctx.beginPath(); - ctx.moveTo(0,0); // Center of the pie - //ctx.scale(1, options.series.pie.tilt); // this actually seems to break everything when here. - ctx.arc(0,0,radius,s.startAngle,s.startAngle+s.angle,false); - ctx.closePath(); - x = mouseX-centerLeft; - y = mouseY-centerTop; - if(ctx.isPointInPath) - { - if (ctx.isPointInPath(mouseX-centerLeft, mouseY-centerTop)) - { - //alert('found slice!'); - ctx.restore(); - return {datapoint: [s.percent, s.data], dataIndex: 0, series: s, seriesIndex: i}; - } - } - else - { - // excanvas for IE doesn;t support isPointInPath, this is a workaround. - p1X = (radius * Math.cos(s.startAngle)); - p1Y = (radius * Math.sin(s.startAngle)); - p2X = (radius * Math.cos(s.startAngle+(s.angle/4))); - p2Y = (radius * Math.sin(s.startAngle+(s.angle/4))); - p3X = (radius * Math.cos(s.startAngle+(s.angle/2))); - p3Y = (radius * Math.sin(s.startAngle+(s.angle/2))); - p4X = (radius * Math.cos(s.startAngle+(s.angle/1.5))); - p4Y = (radius * Math.sin(s.startAngle+(s.angle/1.5))); - p5X = (radius * Math.cos(s.startAngle+s.angle)); - p5Y = (radius * Math.sin(s.startAngle+s.angle)); - arrPoly = [[0,0],[p1X,p1Y],[p2X,p2Y],[p3X,p3Y],[p4X,p4Y],[p5X,p5Y]]; - arrPoint = [x,y]; - // TODO: perhaps do some mathmatical trickery here with the Y-coordinate to compensate for pie tilt? - if(isPointInPoly(arrPoly, arrPoint)) - { - ctx.restore(); - return {datapoint: [s.percent, s.data], dataIndex: 0, series: s, seriesIndex: i}; - } - } - ctx.restore(); - } - } - - return null; - } - - function onMouseMove(e) - { - triggerClickHoverEvent('plothover', e); - } - - function onClick(e) - { - triggerClickHoverEvent('plotclick', e); - } - - // trigger click or hover event (they send the same parameters so we share their code) - function triggerClickHoverEvent(eventname, e) - { - var offset = plot.offset(), - canvasX = parseInt(e.pageX - offset.left), - canvasY = parseInt(e.pageY - offset.top), - item = findNearbySlice(canvasX, canvasY); - - if (options.grid.autoHighlight) - { - // clear auto-highlights - for (var i = 0; i < highlights.length; ++i) - { - var h = highlights[i]; - if (h.auto == eventname && !(item && h.series == item.series)) - unhighlight(h.series); - } - } - - // highlight the slice - if (item) - highlight(item.series, eventname); - - // trigger any hover bind events - var pos = { pageX: e.pageX, pageY: e.pageY }; - target.trigger(eventname, [ pos, item ]); - } - - function highlight(s, auto) - { - if (typeof s == "number") - s = series[s]; - - var i = indexOfHighlight(s); - if (i == -1) - { - highlights.push({ series: s, auto: auto }); - plot.triggerRedrawOverlay(); - } - else if (!auto) - highlights[i].auto = false; - } - - function unhighlight(s) - { - if (s == null) - { - highlights = []; - plot.triggerRedrawOverlay(); - } - - if (typeof s == "number") - s = series[s]; - - var i = indexOfHighlight(s); - if (i != -1) - { - highlights.splice(i, 1); - plot.triggerRedrawOverlay(); - } - } - - function indexOfHighlight(s) - { - for (var i = 0; i < highlights.length; ++i) - { - var h = highlights[i]; - if (h.series == s) - return i; - } - return -1; - } - - function drawOverlay(plot, octx) - { - //alert(options.series.pie.radius); - var options = plot.getOptions(); - //alert(options.series.pie.radius); - - var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; - - octx.save(); - octx.translate(centerLeft, centerTop); - octx.scale(1, options.series.pie.tilt); - - for (i = 0; i < highlights.length; ++i) - drawHighlight(highlights[i].series); - - drawDonutHole(octx); - - octx.restore(); - - function drawHighlight(series) - { - if (series.angle < 0) return; - - //octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString(); - octx.fillStyle = "rgba(255, 255, 255, "+options.series.pie.highlight.opacity+")"; // this is temporary until we have access to parseColor - - octx.beginPath(); - if (Math.abs(series.angle - Math.PI*2) > 0.000000001) - octx.moveTo(0,0); // Center of the pie - octx.arc(0,0,radius,series.startAngle,series.startAngle+series.angle,false); - octx.closePath(); - octx.fill(); - } - - } - - } // end init (plugin body) - - // define pie specific options and their default values - var options = { - series: { - pie: { - show: false, - radius: 'auto', // actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value) - innerRadius:0, /* for donut */ - startAngle: 3/2, - tilt: 1, - offset: { - top: 0, - left: 'auto' - }, - stroke: { - color: '#FFF', - width: 1 - }, - label: { - show: 'auto', - formatter: function(label, slice){ - return '
    '+label+'
    '+Math.round(slice.percent)+'%
    '; - }, // formatter function - radius: 1, // radius at which to place the labels (based on full calculated radius if <=1, or hard pixel value) - background: { - color: null, - opacity: 0 - }, - threshold: 0 // percentage at which to hide the label (i.e. the slice is too narrow) - }, - combine: { - threshold: -1, // percentage at which to combine little slices into one larger slice - color: null, // color to give the new slice (auto-generated if null) - label: 'Other' // label to give the new slice - }, - highlight: { - //color: '#FFF', // will add this functionality once parseColor is available - opacity: 0.5 - } - } - } - }; - - $.plot.plugins.push({ - init: init, - options: options, - name: "pie", - version: "1.0" - }); -})(jQuery); diff --git a/frontend/src/vendor/jquery.flot/jquery.flot.pie.min.js b/frontend/src/vendor/jquery.flot/jquery.flot.pie.min.js deleted file mode 100644 index b7bf870d759..00000000000 --- a/frontend/src/vendor/jquery.flot/jquery.flot.pie.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(b){function c(D){var h=null;var L=null;var n=null;var B=null;var p=null;var M=0;var F=true;var o=10;var w=0.95;var A=0;var d=false;var z=false;var j=[];D.hooks.processOptions.push(g);D.hooks.bindEvents.push(e);function g(O,N){if(N.series.pie.show){N.grid.show=false;if(N.series.pie.label.show=="auto"){if(N.legend.show){N.series.pie.label.show=false}else{N.series.pie.label.show=true}}if(N.series.pie.radius=="auto"){if(N.series.pie.label.show){N.series.pie.radius=3/4}else{N.series.pie.radius=1}}if(N.series.pie.tilt>1){N.series.pie.tilt=1}if(N.series.pie.tilt<0){N.series.pie.tilt=0}O.hooks.processDatapoints.push(E);O.hooks.drawOverlay.push(H);O.hooks.draw.push(r)}}function e(P,N){var O=P.getOptions();if(O.series.pie.show&&O.grid.hoverable){N.unbind("mousemove").mousemove(t)}if(O.series.pie.show&&O.grid.clickable){N.unbind("click").click(l)}}function G(O){var P="";function N(S,T){if(!T){T=0}for(var R=0;Rh.width-n){B=h.width-n}}}function v(O){for(var N=0;N0){R.push({data:[[1,P]],color:N,label:a.series.pie.combine.label,angle:(P*(Math.PI*2))/M,percent:(P/M*100)})}return R}function r(S,Q){if(!L){return}ctx=Q;I();var T=S.getData();var P=0;while(F&&P0){n*=w}P+=1;N();if(a.series.pie.tilt<=0.8){O()}R()}if(P>=o){N();L.prepend('
    Could not draw pie with labels contained inside canvas
    ')}if(S.setSeries&&S.insertLegend){S.setSeries(T);S.insertLegend()}function N(){ctx.clearRect(0,0,h.width,h.height);L.children().filter(".pieLabel, .pieLabelBackground").remove()}function O(){var Z=5;var Y=15;var W=10;var X=0.02;if(a.series.pie.radius>1){var U=a.series.pie.radius}else{var U=n*a.series.pie.radius}if(U>=(h.width/2)-Z||U*a.series.pie.tilt>=(h.height/2)-Y||U<=W){return}ctx.save();ctx.translate(Z,Y);ctx.globalAlpha=X;ctx.fillStyle="#000";ctx.translate(B,p);ctx.scale(1,a.series.pie.tilt);for(var V=1;V<=W;V++){ctx.beginPath();ctx.arc(0,0,U,0,Math.PI*2,false);ctx.fill();U-=V}ctx.restore()}function R(){startAngle=Math.PI*a.series.pie.startAngle;if(a.series.pie.radius>1){var U=a.series.pie.radius}else{var U=n*a.series.pie.radius}ctx.save();ctx.translate(B,p);ctx.scale(1,a.series.pie.tilt);ctx.save();var Y=startAngle;for(var W=0;W1e-9){ctx.moveTo(0,0)}else{if(b.browser.msie){ab-=0.0001}}ctx.arc(0,0,U,Y,Y+ab,false);ctx.closePath();Y+=ab;if(aa){ctx.fill()}else{ctx.stroke()}}function V(){var ac=startAngle;if(a.series.pie.label.radius>1){var Z=a.series.pie.label.radius}else{var Z=n*a.series.pie.label.radius}for(var ab=0;ab=a.series.pie.label.threshold*100){aa(T[ab],ac,ab)}ac+=T[ab].angle}function aa(ap,ai,ag){if(ap.data[0][1]==0){return}var ar=a.legend.labelFormatter,aq,ae=a.series.pie.label.formatter;if(ar){aq=ar(ap.label,ap)}else{aq=ap.label}if(ae){aq=ae(aq,ap)}var aj=((ai+ap.angle)+ai)/2;var ao=B+Math.round(Math.cos(aj)*Z);var am=p+Math.round(Math.sin(aj)*Z)*a.series.pie.tilt;var af=''+aq+"";L.append(af);var an=L.children("#pieLabel"+ag);var ad=(am-an.height()/2);var ah=(ao-an.width()/2);an.css("top",ad);an.css("left",ah);if(0-ad>0||0-ah>0||h.height-(ad+an.height())<0||h.width-(ah+an.width())<0){F=true}if(a.series.pie.label.background.opacity!=0){var ak=a.series.pie.label.background.color;if(ak==null){ak=ap.color}var al="top:"+ad+"px;left:"+ah+"px;";b('
    ').insertBefore(an).css("opacity",a.series.pie.label.background.opacity)}}}}}function J(N){if(a.series.pie.innerRadius>0){N.save();innerRadius=a.series.pie.innerRadius>1?a.series.pie.innerRadius:n*a.series.pie.innerRadius;N.globalCompositeOperation="destination-out";N.beginPath();N.fillStyle=a.series.pie.stroke.color;N.arc(0,0,innerRadius,0,Math.PI*2,false);N.fill();N.closePath();N.restore();N.save();N.beginPath();N.strokeStyle=a.series.pie.stroke.color;N.arc(0,0,innerRadius,0,Math.PI*2,false);N.stroke();N.closePath();N.restore()}}function s(Q,R){for(var S=false,P=-1,N=Q.length,O=N-1;++P1?O.series.pie.radius:n*O.series.pie.radius;for(var Q=0;Q1?P.series.pie.radius:n*P.series.pie.radius;R.save();R.translate(B,p);R.scale(1,P.series.pie.tilt);for(i=0;i1e-9){R.moveTo(0,0)}R.arc(0,0,N,S.startAngle,S.startAngle+S.angle,false);R.closePath();R.fill()}}}var a={series:{pie:{show:false,radius:"auto",innerRadius:0,startAngle:3/2,tilt:1,offset:{top:0,left:"auto"},stroke:{color:"#FFF",width:1},label:{show:"auto",formatter:function(d,e){return'
    '+d+"
    "+Math.round(e.percent)+"%
    "},radius:1,background:{color:null,opacity:0},threshold:0},combine:{threshold:-1,color:null,label:"Other"},highlight:{opacity:0.5}}}};b.plot.plugins.push({init:c,options:a,name:"pie",version:"1.0"})})(jQuery); \ No newline at end of file diff --git a/frontend/src/vendor/jquery.flot/jquery.flot.resize.js b/frontend/src/vendor/jquery.flot/jquery.flot.resize.js deleted file mode 100644 index 69dfb24f38e..00000000000 --- a/frontend/src/vendor/jquery.flot/jquery.flot.resize.js +++ /dev/null @@ -1,60 +0,0 @@ -/* -Flot plugin for automatically redrawing plots when the placeholder -size changes, e.g. on window resizes. - -It works by listening for changes on the placeholder div (through the -jQuery resize event plugin) - if the size changes, it will redraw the -plot. - -There are no options. If you need to disable the plugin for some -plots, you can just fix the size of their placeholders. -*/ - - -/* Inline dependency: - * jQuery resize event - v1.1 - 3/14/2010 - * http://benalman.com/projects/jquery-resize-plugin/ - * - * Copyright (c) 2010 "Cowboy" Ben Alman - * Dual licensed under the MIT and GPL licenses. - * http://benalman.com/about/license/ - */ -(function($,h,c){var a=$([]),e=$.resize=$.extend($.resize,{}),i,k="setTimeout",j="resize",d=j+"-special-event",b="delay",f="throttleWindow";e[b]=250;e[f]=true;$.event.special[j]={setup:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.add(l);$.data(this,d,{w:l.width(),h:l.height()});if(a.length===1){g()}},teardown:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.not(l);l.removeData(d);if(!a.length){clearTimeout(i)}},add:function(l){if(!e[f]&&this[k]){return false}var n;function m(s,o,p){var q=$(this),r=$.data(this,d);r.w=o!==c?o:q.width();r.h=p!==c?p:q.height();n.apply(this,arguments)}if($.isFunction(l)){n=l;return m}else{n=l.handler;l.handler=m}}};function g(){i=h[k](function(){a.each(function(){var n=$(this),m=n.width(),l=n.height(),o=$.data(this,d);if(m!==o.w||l!==o.h){n.trigger(j,[o.w=m,o.h=l])}});g()},e[b])}})(jQuery,this); - - -(function ($) { - var options = { }; // no options - - function init(plot) { - function onResize() { - var placeholder = plot.getPlaceholder(); - - // somebody might have hidden us and we can't plot - // when we don't have the dimensions - if (placeholder.width() == 0 || placeholder.height() == 0) - return; - - plot.resize(); - plot.setupGrid(); - plot.draw(); - } - - function bindEvents(plot, eventHolder) { - plot.getPlaceholder().resize(onResize); - } - - function shutdown(plot, eventHolder) { - plot.getPlaceholder().unbind("resize", onResize); - } - - plot.hooks.bindEvents.push(bindEvents); - plot.hooks.shutdown.push(shutdown); - } - - $.plot.plugins.push({ - init: init, - options: options, - name: 'resize', - version: '1.0' - }); -})(jQuery); diff --git a/frontend/src/vendor/jquery.flot/jquery.flot.resize.min.js b/frontend/src/vendor/jquery.flot/jquery.flot.resize.min.js deleted file mode 100644 index 1fa0771f570..00000000000 --- a/frontend/src/vendor/jquery.flot/jquery.flot.resize.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(n,p,u){var w=n([]),s=n.resize=n.extend(n.resize,{}),o,l="setTimeout",m="resize",t=m+"-special-event",v="delay",r="throttleWindow";s[v]=250;s[r]=true;n.event.special[m]={setup:function(){if(!s[r]&&this[l]){return false}var a=n(this);w=w.add(a);n.data(this,t,{w:a.width(),h:a.height()});if(w.length===1){q()}},teardown:function(){if(!s[r]&&this[l]){return false}var a=n(this);w=w.not(a);a.removeData(t);if(!w.length){clearTimeout(o)}},add:function(b){if(!s[r]&&this[l]){return false}var c;function a(d,h,g){var f=n(this),e=n.data(this,t);e.w=h!==u?h:f.width();e.h=g!==u?g:f.height();c.apply(this,arguments)}if(n.isFunction(b)){c=b;return a}else{c=b.handler;b.handler=a}}};function q(){o=p[l](function(){w.each(function(){var d=n(this),a=d.width(),b=d.height(),c=n.data(this,t);if(a!==c.w||b!==c.h){d.trigger(m,[c.w=a,c.h=b])}});q()},s[v])}})(jQuery,this);(function(b){var a={};function c(f){function e(){var h=f.getPlaceholder();if(h.width()==0||h.height()==0){return}f.resize();f.setupGrid();f.draw()}function g(i,h){i.getPlaceholder().resize(e)}function d(i,h){i.getPlaceholder().unbind("resize",e)}f.hooks.bindEvents.push(g);f.hooks.shutdown.push(d)}b.plot.plugins.push({init:c,options:a,name:"resize",version:"1.0"})})(jQuery); \ No newline at end of file diff --git a/frontend/src/vendor/jquery.flot/jquery.flot.selection.js b/frontend/src/vendor/jquery.flot/jquery.flot.selection.js deleted file mode 100644 index 7f7b32694bd..00000000000 --- a/frontend/src/vendor/jquery.flot/jquery.flot.selection.js +++ /dev/null @@ -1,344 +0,0 @@ -/* -Flot plugin for selecting regions. - -The plugin defines the following options: - - selection: { - mode: null or "x" or "y" or "xy", - color: color - } - -Selection support is enabled by setting the mode to one of "x", "y" or -"xy". In "x" mode, the user will only be able to specify the x range, -similarly for "y" mode. For "xy", the selection becomes a rectangle -where both ranges can be specified. "color" is color of the selection -(if you need to change the color later on, you can get to it with -plot.getOptions().selection.color). - -When selection support is enabled, a "plotselected" event will be -emitted on the DOM element you passed into the plot function. The -event handler gets a parameter with the ranges selected on the axes, -like this: - - placeholder.bind("plotselected", function(event, ranges) { - alert("You selected " + ranges.xaxis.from + " to " + ranges.xaxis.to) - // similar for yaxis - with multiple axes, the extra ones are in - // x2axis, x3axis, ... - }); - -The "plotselected" event is only fired when the user has finished -making the selection. A "plotselecting" event is fired during the -process with the same parameters as the "plotselected" event, in case -you want to know what's happening while it's happening, - -A "plotunselected" event with no arguments is emitted when the user -clicks the mouse to remove the selection. - -The plugin allso adds the following methods to the plot object: - -- setSelection(ranges, preventEvent) - - Set the selection rectangle. The passed in ranges is on the same - form as returned in the "plotselected" event. If the selection mode - is "x", you should put in either an xaxis range, if the mode is "y" - you need to put in an yaxis range and both xaxis and yaxis if the - selection mode is "xy", like this: - - setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } }); - - setSelection will trigger the "plotselected" event when called. If - you don't want that to happen, e.g. if you're inside a - "plotselected" handler, pass true as the second parameter. If you - are using multiple axes, you can specify the ranges on any of those, - e.g. as x2axis/x3axis/... instead of xaxis, the plugin picks the - first one it sees. - -- clearSelection(preventEvent) - - Clear the selection rectangle. Pass in true to avoid getting a - "plotunselected" event. - -- getSelection() - - Returns the current selection in the same format as the - "plotselected" event. If there's currently no selection, the - function returns null. - -*/ - -(function ($) { - function init(plot) { - var selection = { - first: { x: -1, y: -1}, second: { x: -1, y: -1}, - show: false, - active: false - }; - - // FIXME: The drag handling implemented here should be - // abstracted out, there's some similar code from a library in - // the navigation plugin, this should be massaged a bit to fit - // the Flot cases here better and reused. Doing this would - // make this plugin much slimmer. - var savedhandlers = {}; - - var mouseUpHandler = null; - - function onMouseMove(e) { - if (selection.active) { - updateSelection(e); - - plot.getPlaceholder().trigger("plotselecting", [ getSelection() ]); - } - } - - function onMouseDown(e) { - if (e.which != 1) // only accept left-click - return; - - // cancel out any text selections - document.body.focus(); - - // prevent text selection and drag in old-school browsers - if (document.onselectstart !== undefined && savedhandlers.onselectstart == null) { - savedhandlers.onselectstart = document.onselectstart; - document.onselectstart = function () { return false; }; - } - if (document.ondrag !== undefined && savedhandlers.ondrag == null) { - savedhandlers.ondrag = document.ondrag; - document.ondrag = function () { return false; }; - } - - setSelectionPos(selection.first, e); - - selection.active = true; - - // this is a bit silly, but we have to use a closure to be - // able to whack the same handler again - mouseUpHandler = function (e) { onMouseUp(e); }; - - $(document).one("mouseup", mouseUpHandler); - } - - function onMouseUp(e) { - mouseUpHandler = null; - - // revert drag stuff for old-school browsers - if (document.onselectstart !== undefined) - document.onselectstart = savedhandlers.onselectstart; - if (document.ondrag !== undefined) - document.ondrag = savedhandlers.ondrag; - - // no more dragging - selection.active = false; - updateSelection(e); - - if (selectionIsSane()) - triggerSelectedEvent(); - else { - // this counts as a clear - plot.getPlaceholder().trigger("plotunselected", [ ]); - plot.getPlaceholder().trigger("plotselecting", [ null ]); - } - - return false; - } - - function getSelection() { - if (!selectionIsSane()) - return null; - - var r = {}, c1 = selection.first, c2 = selection.second; - $.each(plot.getAxes(), function (name, axis) { - if (axis.used) { - var p1 = axis.c2p(c1[axis.direction]), p2 = axis.c2p(c2[axis.direction]); - r[name] = { from: Math.min(p1, p2), to: Math.max(p1, p2) }; - } - }); - return r; - } - - function triggerSelectedEvent() { - var r = getSelection(); - - plot.getPlaceholder().trigger("plotselected", [ r ]); - - // backwards-compat stuff, to be removed in future - if (r.xaxis && r.yaxis) - plot.getPlaceholder().trigger("selected", [ { x1: r.xaxis.from, y1: r.yaxis.from, x2: r.xaxis.to, y2: r.yaxis.to } ]); - } - - function clamp(min, value, max) { - return value < min ? min: (value > max ? max: value); - } - - function setSelectionPos(pos, e) { - var o = plot.getOptions(); - var offset = plot.getPlaceholder().offset(); - var plotOffset = plot.getPlotOffset(); - pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plot.width()); - pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, plot.height()); - - if (o.selection.mode == "y") - pos.x = pos == selection.first ? 0 : plot.width(); - - if (o.selection.mode == "x") - pos.y = pos == selection.first ? 0 : plot.height(); - } - - function updateSelection(pos) { - if (pos.pageX == null) - return; - - setSelectionPos(selection.second, pos); - if (selectionIsSane()) { - selection.show = true; - plot.triggerRedrawOverlay(); - } - else - clearSelection(true); - } - - function clearSelection(preventEvent) { - if (selection.show) { - selection.show = false; - plot.triggerRedrawOverlay(); - if (!preventEvent) - plot.getPlaceholder().trigger("plotunselected", [ ]); - } - } - - // function taken from markings support in Flot - function extractRange(ranges, coord) { - var axis, from, to, key, axes = plot.getAxes(); - - for (var k in axes) { - axis = axes[k]; - if (axis.direction == coord) { - key = coord + axis.n + "axis"; - if (!ranges[key] && axis.n == 1) - key = coord + "axis"; // support x1axis as xaxis - if (ranges[key]) { - from = ranges[key].from; - to = ranges[key].to; - break; - } - } - } - - // backwards-compat stuff - to be removed in future - if (!ranges[key]) { - axis = coord == "x" ? plot.getXAxes()[0] : plot.getYAxes()[0]; - from = ranges[coord + "1"]; - to = ranges[coord + "2"]; - } - - // auto-reverse as an added bonus - if (from != null && to != null && from > to) { - var tmp = from; - from = to; - to = tmp; - } - - return { from: from, to: to, axis: axis }; - } - - function setSelection(ranges, preventEvent) { - var axis, range, o = plot.getOptions(); - - if (o.selection.mode == "y") { - selection.first.x = 0; - selection.second.x = plot.width(); - } - else { - range = extractRange(ranges, "x"); - - selection.first.x = range.axis.p2c(range.from); - selection.second.x = range.axis.p2c(range.to); - } - - if (o.selection.mode == "x") { - selection.first.y = 0; - selection.second.y = plot.height(); - } - else { - range = extractRange(ranges, "y"); - - selection.first.y = range.axis.p2c(range.from); - selection.second.y = range.axis.p2c(range.to); - } - - selection.show = true; - plot.triggerRedrawOverlay(); - if (!preventEvent && selectionIsSane()) - triggerSelectedEvent(); - } - - function selectionIsSane() { - var minSize = 5; - return Math.abs(selection.second.x - selection.first.x) >= minSize && - Math.abs(selection.second.y - selection.first.y) >= minSize; - } - - plot.clearSelection = clearSelection; - plot.setSelection = setSelection; - plot.getSelection = getSelection; - - plot.hooks.bindEvents.push(function(plot, eventHolder) { - var o = plot.getOptions(); - if (o.selection.mode != null) { - eventHolder.mousemove(onMouseMove); - eventHolder.mousedown(onMouseDown); - } - }); - - - plot.hooks.drawOverlay.push(function (plot, ctx) { - // draw selection - if (selection.show && selectionIsSane()) { - var plotOffset = plot.getPlotOffset(); - var o = plot.getOptions(); - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - var c = $.color.parse(o.selection.color); - - ctx.strokeStyle = c.scale('a', 0.8).toString(); - ctx.lineWidth = 1; - ctx.lineJoin = "round"; - ctx.fillStyle = c.scale('a', 0.4).toString(); - - var x = Math.min(selection.first.x, selection.second.x), - y = Math.min(selection.first.y, selection.second.y), - w = Math.abs(selection.second.x - selection.first.x), - h = Math.abs(selection.second.y - selection.first.y); - - ctx.fillRect(x, y, w, h); - ctx.strokeRect(x, y, w, h); - - ctx.restore(); - } - }); - - plot.hooks.shutdown.push(function (plot, eventHolder) { - eventHolder.unbind("mousemove", onMouseMove); - eventHolder.unbind("mousedown", onMouseDown); - - if (mouseUpHandler) - $(document).unbind("mouseup", mouseUpHandler); - }); - - } - - $.plot.plugins.push({ - init: init, - options: { - selection: { - mode: null, // one of null, "x", "y" or "xy" - color: "#e8cfac" - } - }, - name: 'selection', - version: '1.1' - }); -})(jQuery); diff --git a/frontend/src/vendor/jquery.flot/jquery.flot.selection.min.js b/frontend/src/vendor/jquery.flot/jquery.flot.selection.min.js deleted file mode 100644 index badc0052dbe..00000000000 --- a/frontend/src/vendor/jquery.flot/jquery.flot.selection.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(a){function b(k){var p={first:{x:-1,y:-1},second:{x:-1,y:-1},show:false,active:false};var m={};var r=null;function e(s){if(p.active){l(s);k.getPlaceholder().trigger("plotselecting",[g()])}}function n(s){if(s.which!=1){return}document.body.focus();if(document.onselectstart!==undefined&&m.onselectstart==null){m.onselectstart=document.onselectstart;document.onselectstart=function(){return false}}if(document.ondrag!==undefined&&m.ondrag==null){m.ondrag=document.ondrag;document.ondrag=function(){return false}}d(p.first,s);p.active=true;r=function(t){j(t)};a(document).one("mouseup",r)}function j(s){r=null;if(document.onselectstart!==undefined){document.onselectstart=m.onselectstart}if(document.ondrag!==undefined){document.ondrag=m.ondrag}p.active=false;l(s);if(f()){i()}else{k.getPlaceholder().trigger("plotunselected",[]);k.getPlaceholder().trigger("plotselecting",[null])}return false}function g(){if(!f()){return null}var u={},t=p.first,s=p.second;a.each(k.getAxes(),function(v,w){if(w.used){var y=w.c2p(t[w.direction]),x=w.c2p(s[w.direction]);u[v]={from:Math.min(y,x),to:Math.max(y,x)}}});return u}function i(){var s=g();k.getPlaceholder().trigger("plotselected",[s]);if(s.xaxis&&s.yaxis){k.getPlaceholder().trigger("selected",[{x1:s.xaxis.from,y1:s.yaxis.from,x2:s.xaxis.to,y2:s.yaxis.to}])}}function h(t,u,s){return us?s:u)}function d(w,t){var v=k.getOptions();var u=k.getPlaceholder().offset();var s=k.getPlotOffset();w.x=h(0,t.pageX-u.left-s.left,k.width());w.y=h(0,t.pageY-u.top-s.top,k.height());if(v.selection.mode=="y"){w.x=w==p.first?0:k.width()}if(v.selection.mode=="x"){w.y=w==p.first?0:k.height()}}function l(s){if(s.pageX==null){return}d(p.second,s);if(f()){p.show=true;k.triggerRedrawOverlay()}else{q(true)}}function q(s){if(p.show){p.show=false;k.triggerRedrawOverlay();if(!s){k.getPlaceholder().trigger("plotunselected",[])}}}function c(s,w){var t,y,z,A,x=k.getAxes();for(var u in x){t=x[u];if(t.direction==w){A=w+t.n+"axis";if(!s[A]&&t.n==1){A=w+"axis"}if(s[A]){y=s[A].from;z=s[A].to;break}}}if(!s[A]){t=w=="x"?k.getXAxes()[0]:k.getYAxes()[0];y=s[w+"1"];z=s[w+"2"]}if(y!=null&&z!=null&&y>z){var v=y;y=z;z=v}return{from:y,to:z,axis:t}}function o(t,s){var v,u,w=k.getOptions();if(w.selection.mode=="y"){p.first.x=0;p.second.x=k.width()}else{u=c(t,"x");p.first.x=u.axis.p2c(u.from);p.second.x=u.axis.p2c(u.to)}if(w.selection.mode=="x"){p.first.y=0;p.second.y=k.height()}else{u=c(t,"y");p.first.y=u.axis.p2c(u.from);p.second.y=u.axis.p2c(u.to)}p.show=true;k.triggerRedrawOverlay();if(!s&&f()){i()}}function f(){var s=5;return Math.abs(p.second.x-p.first.x)>=s&&Math.abs(p.second.y-p.first.y)>=s}k.clearSelection=q;k.setSelection=o;k.getSelection=g;k.hooks.bindEvents.push(function(t,s){var u=t.getOptions();if(u.selection.mode!=null){s.mousemove(e);s.mousedown(n)}});k.hooks.drawOverlay.push(function(v,D){if(p.show&&f()){var t=v.getPlotOffset();var s=v.getOptions();D.save();D.translate(t.left,t.top);var z=a.color.parse(s.selection.color);D.strokeStyle=z.scale("a",0.8).toString();D.lineWidth=1;D.lineJoin="round";D.fillStyle=z.scale("a",0.4).toString();var B=Math.min(p.first.x,p.second.x),A=Math.min(p.first.y,p.second.y),C=Math.abs(p.second.x-p.first.x),u=Math.abs(p.second.y-p.first.y);D.fillRect(B,A,C,u);D.strokeRect(B,A,C,u);D.restore()}});k.hooks.shutdown.push(function(t,s){s.unbind("mousemove",e);s.unbind("mousedown",n);if(r){a(document).unbind("mouseup",r)}})}a.plot.plugins.push({init:b,options:{selection:{mode:null,color:"#e8cfac"}},name:"selection",version:"1.1"})})(jQuery); \ No newline at end of file diff --git a/frontend/src/vendor/jquery.flot/jquery.flot.stack.js b/frontend/src/vendor/jquery.flot/jquery.flot.stack.js deleted file mode 100644 index a31d5dc9b58..00000000000 --- a/frontend/src/vendor/jquery.flot/jquery.flot.stack.js +++ /dev/null @@ -1,184 +0,0 @@ -/* -Flot plugin for stacking data sets, i.e. putting them on top of each -other, for accumulative graphs. - -The plugin assumes the data is sorted on x (or y if stacking -horizontally). For line charts, it is assumed that if a line has an -undefined gap (from a null point), then the line above it should have -the same gap - insert zeros instead of "null" if you want another -behaviour. This also holds for the start and end of the chart. Note -that stacking a mix of positive and negative values in most instances -doesn't make sense (so it looks weird). - -Two or more series are stacked when their "stack" attribute is set to -the same key (which can be any number or string or just "true"). To -specify the default stack, you can set - - series: { - stack: null or true or key (number/string) - } - -or specify it for a specific series - - $.plot($("#placeholder"), [{ data: [ ... ], stack: true }]) - -The stacking order is determined by the order of the data series in -the array (later series end up on top of the previous). - -Internally, the plugin modifies the datapoints in each series, adding -an offset to the y value. For line series, extra data points are -inserted through interpolation. If there's a second y value, it's also -adjusted (e.g for bar charts or filled areas). -*/ - -(function ($) { - var options = { - series: { stack: null } // or number/string - }; - - function init(plot) { - function findMatchingSeries(s, allseries) { - var res = null - for (var i = 0; i < allseries.length; ++i) { - if (s == allseries[i]) - break; - - if (allseries[i].stack == s.stack) - res = allseries[i]; - } - - return res; - } - - function stackData(plot, s, datapoints) { - if (s.stack == null) - return; - - var other = findMatchingSeries(s, plot.getData()); - if (!other) - return; - - var ps = datapoints.pointsize, - points = datapoints.points, - otherps = other.datapoints.pointsize, - otherpoints = other.datapoints.points, - newpoints = [], - px, py, intery, qx, qy, bottom, - withlines = s.lines.show, - horizontal = s.bars.horizontal, - withbottom = ps > 2 && (horizontal ? datapoints.format[2].x : datapoints.format[2].y), - withsteps = withlines && s.lines.steps, - fromgap = true, - keyOffset = horizontal ? 1 : 0, - accumulateOffset = horizontal ? 0 : 1, - i = 0, j = 0, l; - - while (true) { - if (i >= points.length) - break; - - l = newpoints.length; - - if (points[i] == null) { - // copy gaps - for (m = 0; m < ps; ++m) - newpoints.push(points[i + m]); - i += ps; - } - else if (j >= otherpoints.length) { - // for lines, we can't use the rest of the points - if (!withlines) { - for (m = 0; m < ps; ++m) - newpoints.push(points[i + m]); - } - i += ps; - } - else if (otherpoints[j] == null) { - // oops, got a gap - for (m = 0; m < ps; ++m) - newpoints.push(null); - fromgap = true; - j += otherps; - } - else { - // cases where we actually got two points - px = points[i + keyOffset]; - py = points[i + accumulateOffset]; - qx = otherpoints[j + keyOffset]; - qy = otherpoints[j + accumulateOffset]; - bottom = 0; - - if (px == qx) { - for (m = 0; m < ps; ++m) - newpoints.push(points[i + m]); - - newpoints[l + accumulateOffset] += qy; - bottom = qy; - - i += ps; - j += otherps; - } - else if (px > qx) { - // we got past point below, might need to - // insert interpolated extra point - if (withlines && i > 0 && points[i - ps] != null) { - intery = py + (points[i - ps + accumulateOffset] - py) * (qx - px) / (points[i - ps + keyOffset] - px); - newpoints.push(qx); - newpoints.push(intery + qy); - for (m = 2; m < ps; ++m) - newpoints.push(points[i + m]); - bottom = qy; - } - - j += otherps; - } - else { // px < qx - if (fromgap && withlines) { - // if we come from a gap, we just skip this point - i += ps; - continue; - } - - for (m = 0; m < ps; ++m) - newpoints.push(points[i + m]); - - // we might be able to interpolate a point below, - // this can give us a better y - if (withlines && j > 0 && otherpoints[j - otherps] != null) - bottom = qy + (otherpoints[j - otherps + accumulateOffset] - qy) * (px - qx) / (otherpoints[j - otherps + keyOffset] - qx); - - newpoints[l + accumulateOffset] += bottom; - - i += ps; - } - - fromgap = false; - - if (l != newpoints.length && withbottom) - newpoints[l + 2] += bottom; - } - - // maintain the line steps invariant - if (withsteps && l != newpoints.length && l > 0 - && newpoints[l] != null - && newpoints[l] != newpoints[l - ps] - && newpoints[l + 1] != newpoints[l - ps + 1]) { - for (m = 0; m < ps; ++m) - newpoints[l + ps + m] = newpoints[l + m]; - newpoints[l + 1] = newpoints[l - ps + 1]; - } - } - - datapoints.points = newpoints; - } - - plot.hooks.processDatapoints.push(stackData); - } - - $.plot.plugins.push({ - init: init, - options: options, - name: 'stack', - version: '1.2' - }); -})(jQuery); diff --git a/frontend/src/vendor/jquery.flot/jquery.flot.stack.min.js b/frontend/src/vendor/jquery.flot/jquery.flot.stack.min.js deleted file mode 100644 index bba2a0e5ff7..00000000000 --- a/frontend/src/vendor/jquery.flot/jquery.flot.stack.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(b){var a={series:{stack:null}};function c(f){function d(k,j){var h=null;for(var g=0;g2&&(G?g.format[2].x:g.format[2].y),n=u&&v.lines.steps,E=true,q=G?1:0,H=G?0:1,D=0,B=0,A;while(true){if(D>=F.length){break}A=t.length;if(F[D]==null){for(m=0;m=y.length){if(!u){for(m=0;mJ){if(u&&D>0&&F[D-z]!=null){k=w+(F[D-z+H]-w)*(J-x)/(F[D-z+q]-x);t.push(J);t.push(k+I);for(m=2;m0&&y[B-h]!=null){r=I+(y[B-h+H]-I)*(x-J)/(y[B-h+q]-J)}t[A+H]+=r;D+=z}}E=false;if(A!=t.length&&o){t[A+2]+=r}}}}if(n&&A!=t.length&&A>0&&t[A]!=null&&t[A]!=t[A-z]&&t[A+1]!=t[A-z+1]){for(m=0;m s = r * sqrt(pi)/2 - var size = radius * Math.sqrt(Math.PI) / 2; - ctx.rect(x - size, y - size, size + size, size + size); - }, - diamond: function (ctx, x, y, radius, shadow) { - // pi * r^2 = 2s^2 => s = r * sqrt(pi/2) - var size = radius * Math.sqrt(Math.PI / 2); - ctx.moveTo(x - size, y); - ctx.lineTo(x, y - size); - ctx.lineTo(x + size, y); - ctx.lineTo(x, y + size); - ctx.lineTo(x - size, y); - }, - triangle: function (ctx, x, y, radius, shadow) { - // pi * r^2 = 1/2 * s^2 * sin (pi / 3) => s = r * sqrt(2 * pi / sin(pi / 3)) - var size = radius * Math.sqrt(2 * Math.PI / Math.sin(Math.PI / 3)); - var height = size * Math.sin(Math.PI / 3); - ctx.moveTo(x - size/2, y + height/2); - ctx.lineTo(x + size/2, y + height/2); - if (!shadow) { - ctx.lineTo(x, y - height/2); - ctx.lineTo(x - size/2, y + height/2); - } - }, - cross: function (ctx, x, y, radius, shadow) { - // pi * r^2 = (2s)^2 => s = r * sqrt(pi)/2 - var size = radius * Math.sqrt(Math.PI) / 2; - ctx.moveTo(x - size, y - size); - ctx.lineTo(x + size, y + size); - ctx.moveTo(x - size, y + size); - ctx.lineTo(x + size, y - size); - } - } - - var s = series.points.symbol; - if (handlers[s]) - series.points.symbol = handlers[s]; - } - - function init(plot) { - plot.hooks.processDatapoints.push(processRawData); - } - - $.plot.plugins.push({ - init: init, - name: 'symbols', - version: '1.0' - }); -})(jQuery); diff --git a/frontend/src/vendor/jquery.flot/jquery.flot.symbol.min.js b/frontend/src/vendor/jquery.flot/jquery.flot.symbol.min.js deleted file mode 100644 index 272e003ab49..00000000000 --- a/frontend/src/vendor/jquery.flot/jquery.flot.symbol.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(b){function a(h,e,g){var d={square:function(k,j,n,i,m){var l=i*Math.sqrt(Math.PI)/2;k.rect(j-l,n-l,l+l,l+l)},diamond:function(k,j,n,i,m){var l=i*Math.sqrt(Math.PI/2);k.moveTo(j-l,n);k.lineTo(j,n-l);k.lineTo(j+l,n);k.lineTo(j,n+l);k.lineTo(j-l,n)},triangle:function(l,k,o,j,n){var m=j*Math.sqrt(2*Math.PI/Math.sin(Math.PI/3));var i=m*Math.sin(Math.PI/3);l.moveTo(k-m/2,o+i/2);l.lineTo(k+m/2,o+i/2);if(!n){l.lineTo(k,o-i/2);l.lineTo(k-m/2,o+i/2)}},cross:function(k,j,n,i,m){var l=i*Math.sqrt(Math.PI)/2;k.moveTo(j-l,n-l);k.lineTo(j+l,n+l);k.moveTo(j-l,n+l);k.lineTo(j+l,n-l)}};var f=e.points.symbol;if(d[f]){e.points.symbol=d[f]}}function c(d){d.hooks.processDatapoints.push(a)}b.plot.plugins.push({init:c,name:"symbols",version:"1.0"})})(jQuery); \ No newline at end of file diff --git a/frontend/src/vendor/jquery.flot/jquery.flot.threshold.js b/frontend/src/vendor/jquery.flot/jquery.flot.threshold.js deleted file mode 100644 index 0b2e7ac82a7..00000000000 --- a/frontend/src/vendor/jquery.flot/jquery.flot.threshold.js +++ /dev/null @@ -1,103 +0,0 @@ -/* -Flot plugin for thresholding data. Controlled through the option -"threshold" in either the global series options - - series: { - threshold: { - below: number - color: colorspec - } - } - -or in a specific series - - $.plot($("#placeholder"), [{ data: [ ... ], threshold: { ... }}]) - -The data points below "below" are drawn with the specified color. This -makes it easy to mark points below 0, e.g. for budget data. - -Internally, the plugin works by splitting the data into two series, -above and below the threshold. The extra series below the threshold -will have its label cleared and the special "originSeries" attribute -set to the original series. You may need to check for this in hover -events. -*/ - -(function ($) { - var options = { - series: { threshold: null } // or { below: number, color: color spec} - }; - - function init(plot) { - function thresholdData(plot, s, datapoints) { - if (!s.threshold) - return; - - var ps = datapoints.pointsize, i, x, y, p, prevp, - thresholded = $.extend({}, s); // note: shallow copy - - thresholded.datapoints = { points: [], pointsize: ps }; - thresholded.label = null; - thresholded.color = s.threshold.color; - thresholded.threshold = null; - thresholded.originSeries = s; - thresholded.data = []; - - var below = s.threshold.below, - origpoints = datapoints.points, - addCrossingPoints = s.lines.show; - - threspoints = []; - newpoints = []; - - for (i = 0; i < origpoints.length; i += ps) { - x = origpoints[i] - y = origpoints[i + 1]; - - prevp = p; - if (y < below) - p = threspoints; - else - p = newpoints; - - if (addCrossingPoints && prevp != p && x != null - && i > 0 && origpoints[i - ps] != null) { - var interx = (x - origpoints[i - ps]) / (y - origpoints[i - ps + 1]) * (below - y) + x; - prevp.push(interx); - prevp.push(below); - for (m = 2; m < ps; ++m) - prevp.push(origpoints[i + m]); - - p.push(null); // start new segment - p.push(null); - for (m = 2; m < ps; ++m) - p.push(origpoints[i + m]); - p.push(interx); - p.push(below); - for (m = 2; m < ps; ++m) - p.push(origpoints[i + m]); - } - - p.push(x); - p.push(y); - } - - datapoints.points = newpoints; - thresholded.datapoints.points = threspoints; - - if (thresholded.datapoints.points.length > 0) - plot.getData().push(thresholded); - - // FIXME: there are probably some edge cases left in bars - } - - plot.hooks.processDatapoints.push(thresholdData); - } - - $.plot.plugins.push({ - init: init, - options: options, - name: 'threshold', - version: '1.0' - }); -})(jQuery); diff --git a/frontend/src/vendor/jquery.flot/jquery.flot.threshold.min.js b/frontend/src/vendor/jquery.flot/jquery.flot.threshold.min.js deleted file mode 100644 index d8b79dfc93c..00000000000 --- a/frontend/src/vendor/jquery.flot/jquery.flot.threshold.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(B){var A={series:{threshold:null}};function C(D){function E(L,S,M){if(!S.threshold){return }var F=M.pointsize,I,O,N,G,K,H=B.extend({},S);H.datapoints={points:[],pointsize:F};H.label=null;H.color=S.threshold.color;H.threshold=null;H.originSeries=S;H.data=[];var P=S.threshold.below,Q=M.points,R=S.lines.show;threspoints=[];newpoints=[];for(I=0;I0&&Q[I-F]!=null){var J=(O-Q[I-F])/(N-Q[I-F+1])*(P-N)+O;K.push(J);K.push(P);for(m=2;m0){L.getData().push(H)}}D.hooks.processDatapoints.push(E)}B.plot.plugins.push({init:C,options:A,name:"threshold",version:"1.0"})})(jQuery); \ No newline at end of file From 741d01644ab99670713c4b873d00cf40c549a87d Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Thu, 29 Jan 2026 17:38:21 -0300 Subject: [PATCH 075/293] Show action buttons in Taskboard/Burndown headers Updates `SprintPageHeaderComponent` to support `with_action_button`. --- .../sprint_page_header_component.html.erb | 17 ++++++----------- .../backlogs/sprint_page_header_component.rb | 4 ++++ .../app/views/rb_burndown_charts/show.html.erb | 15 ++++++++++++++- .../app/views/rb_taskboards/show.html.erb | 16 +++++++++++++++- 4 files changed, 39 insertions(+), 13 deletions(-) diff --git a/modules/backlogs/app/components/backlogs/sprint_page_header_component.html.erb b/modules/backlogs/app/components/backlogs/sprint_page_header_component.html.erb index 7ddc8a20264..7b8ba671461 100644 --- a/modules/backlogs/app/components/backlogs/sprint_page_header_component.html.erb +++ b/modules/backlogs/app/components/backlogs/sprint_page_header_component.html.erb @@ -27,14 +27,9 @@ See COPYRIGHT and LICENSE files for more details. ++# %> -<%= - render Primer::OpenProject::PageHeader.new do |header| - header.with_title_content(@sprint.name) - - header.with_description do - format_date_range(date_range) - end - - header.with_breadcrumbs(breadcrumb_items) - end -%> +<%= render(@page_header) do |header| %> + <% header.with_title_content(@sprint.name) %> + <% header.with_description { format_date_range(date_range) } %> + <% header.with_breadcrumbs(breadcrumb_items) %> + <%= content %> +<% end %> diff --git a/modules/backlogs/app/components/backlogs/sprint_page_header_component.rb b/modules/backlogs/app/components/backlogs/sprint_page_header_component.rb index c3e0fdb1204..f0b54587d94 100644 --- a/modules/backlogs/app/components/backlogs/sprint_page_header_component.rb +++ b/modules/backlogs/app/components/backlogs/sprint_page_header_component.rb @@ -33,11 +33,15 @@ module Backlogs include ApplicationHelper include RbCommonHelper + delegate :with_action_button, to: :@page_header + def initialize(sprint:, project:) super @sprint = sprint @project = project + + @page_header = Primer::OpenProject::PageHeader.new end def breadcrumb_items diff --git a/modules/backlogs/app/views/rb_burndown_charts/show.html.erb b/modules/backlogs/app/views/rb_burndown_charts/show.html.erb index 86ea72309d8..5d052463745 100644 --- a/modules/backlogs/app/views/rb_burndown_charts/show.html.erb +++ b/modules/backlogs/app/views/rb_burndown_charts/show.html.erb @@ -33,7 +33,20 @@ See COPYRIGHT and LICENSE files for more details. <% end -%> -<%= render(Backlogs::SprintPageHeaderComponent.new(sprint: @sprint, project: @project)) %> +<%= + render(Backlogs::SprintPageHeaderComponent.new(sprint: @sprint, project: @project)) do |header| + header.with_action_button( + tag: :a, + href: backlogs_project_sprint_taskboard_path(@project, @sprint), + mobile_icon: :"op-view-cards", + mobile_label: t(:label_task_board), + aria: { label: t(:label_task_board) } + ) do |button| + button.with_leading_visual_icon(icon: :"op-view-cards") + t(:label_task_board) + end + end +%> <% if @burndown %> <%= render partial: "burndown", locals: { div: "burndown_", burndown: @burndown } %> diff --git a/modules/backlogs/app/views/rb_taskboards/show.html.erb b/modules/backlogs/app/views/rb_taskboards/show.html.erb index acae533782b..23dc8eb7a2d 100644 --- a/modules/backlogs/app/views/rb_taskboards/show.html.erb +++ b/modules/backlogs/app/views/rb_taskboards/show.html.erb @@ -35,7 +35,21 @@ See COPYRIGHT and LICENSE files for more details. <% html_title @sprint.name %> -<%= render(Backlogs::SprintPageHeaderComponent.new(sprint: @sprint, project: @project)) %> +<%= + render(Backlogs::SprintPageHeaderComponent.new(sprint: @sprint, project: @project)) do |header| + header.with_action_button( + tag: :a, + href: backlogs_project_sprint_burndown_chart_path(@project, @sprint), + mobile_icon: :graph, + mobile_label: t(:"backlogs.show_burndown_chart"), + aria: { label: t(:"backlogs.show_burndown_chart") }, + disabled: !@sprint.has_burndown? + ) do |button| + button.with_leading_visual_icon(icon: :graph) + t(:"backlogs.show_burndown_chart") + end + end +%> <%= render(Primer::OpenProject::SubHeader.new) do |component| %> <% component.with_filter_component(id: "col_width") do %> From 16ca9063a6e0e9e2a30b3faa1b07af70d61e8fb1 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 19:55:54 -0300 Subject: [PATCH 076/293] Use `fetch_or_fallback` for state validation in BacklogHeaderComponent (#21814) * Initial plan * Use fetch_or_fallback to validate state in BacklogHeaderComponent Co-authored-by: myabc <755+myabc@users.noreply.github.com> * Add test for state validation fallback behavior Co-authored-by: myabc <755+myabc@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: myabc <755+myabc@users.noreply.github.com> --- .../app/components/backlogs/backlog_header_component.rb | 3 ++- .../components/backlogs/backlog_header_component_spec.rb | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/modules/backlogs/app/components/backlogs/backlog_header_component.rb b/modules/backlogs/app/components/backlogs/backlog_header_component.rb index fd81a433cb7..6daeacba6ac 100644 --- a/modules/backlogs/app/components/backlogs/backlog_header_component.rb +++ b/modules/backlogs/app/components/backlogs/backlog_header_component.rb @@ -32,6 +32,7 @@ module Backlogs class BacklogHeaderComponent < ApplicationComponent include OpPrimer::ComponentHelpers include OpTurbo::Streamable + include Primer::FetchOrFallbackHelper include Redmine::I18n include RbCommonHelper @@ -55,7 +56,7 @@ module Backlogs @backlog = backlog @project = project - @state = ActiveSupport::StringInquirer.new(state.to_s) + @state = ActiveSupport::StringInquirer.new(fetch_or_fallback(STATE_OPTIONS, state, STATE_DEFAULT).to_s) @collapsed = folded @current_user = current_user end diff --git a/modules/backlogs/spec/components/backlogs/backlog_header_component_spec.rb b/modules/backlogs/spec/components/backlogs/backlog_header_component_spec.rb index cd7e44a1d73..ded99a4092f 100644 --- a/modules/backlogs/spec/components/backlogs/backlog_header_component_spec.rb +++ b/modules/backlogs/spec/components/backlogs/backlog_header_component_spec.rb @@ -211,4 +211,11 @@ RSpec.describe Backlogs::BacklogHeaderComponent, type: :component do expect(page).to have_link(I18n.t(:button_cancel)) end end + + describe "state validation" do + it "raises an InvalidValueError for invalid state values" do + expect { render_component(state: :invalid) } + .to raise_error(Primer::FetchOrFallbackHelper::InvalidValueError) + end + end end From df2e2aa0628aaa9d1c5c0604a7f6166db7fd4668 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Fri, 30 Jan 2026 18:40:18 -0300 Subject: [PATCH 077/293] Refresh backlogs list on story update via spllt view - Moves the backlogs list into its own Turbo Frame. - Hooks into `HalEventsService` to subscribe to work package updates. - Refreshes the Turbo Frame (with morphing) on work package update. --- .../app/features/plugins/plugin-context.ts | 2 ++ .../dynamic/backlogs.controller.ts | 36 ++++++++++++++++++- .../rb_master_backlogs_controller.rb | 34 ++++++++++-------- .../views/rb_master_backlogs/_list.html.erb | 23 ++++++++++++ .../views/rb_master_backlogs/index.html.erb | 30 +++------------- modules/backlogs/config/routes.rb | 14 +++----- .../lib/open_project/backlogs/engine.rb | 2 +- .../rb_master_backlogs_controller_spec.rb | 27 +++++++++++--- .../rb_master_backlogs_routing_spec.rb | 2 +- 9 files changed, 114 insertions(+), 56 deletions(-) create mode 100644 modules/backlogs/app/views/rb_master_backlogs/_list.html.erb diff --git a/frontend/src/app/features/plugins/plugin-context.ts b/frontend/src/app/features/plugins/plugin-context.ts index 2dbd58f1d8f..c51d0a50d79 100644 --- a/frontend/src/app/features/plugins/plugin-context.ts +++ b/frontend/src/app/features/plugins/plugin-context.ts @@ -31,6 +31,7 @@ import { HttpClient } from '@angular/common/http'; import { TimezoneService } from 'core-app/core/datetime/timezone.service'; import { TurboRequestsService } from 'core-app/core/turbo/turbo-requests.service'; import { CurrentProjectService } from 'core-app/core/current-project/current-project.service'; +import { HalEventsService } from '../hal/services/hal-events.service'; /** * Plugin context bridge for plugins outside the CLI compiler context * in order to access services and parts of the core application @@ -48,6 +49,7 @@ export class OpenProjectPluginContext { confirmDialog: this.injector.get(ConfirmDialogService), externalQueryConfiguration: this.injector.get(ExternalQueryConfigurationService), externalRelationQueryConfiguration: this.injector.get(ExternalRelationQueryConfigurationService), + halEvents: this.injector.get(HalEventsService), halResource: this.injector.get(HalResourceService), hooks: this.injector.get(HookService), i18n: this.injector.get(I18nService), diff --git a/frontend/src/stimulus/controllers/dynamic/backlogs.controller.ts b/frontend/src/stimulus/controllers/dynamic/backlogs.controller.ts index b2ed8978f20..768dcb9a258 100644 --- a/frontend/src/stimulus/controllers/dynamic/backlogs.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/backlogs.controller.ts @@ -27,6 +27,40 @@ //++ import { Controller } from '@hotwired/stimulus'; +import { FrameElement } from '@hotwired/turbo'; +import { HalEventsService } from 'core-app/features/hal/services/hal-events.service'; +import { filter, Subscription } from 'rxjs'; -export default class BacklogsController extends Controller { +export default class BacklogsController extends Controller { + static values = { + listUrl: String, + }; + + declare listUrlValue:string; + private service:HalEventsService|null = null; + private subscription:Subscription|null = null; + + // eslint-disable-next-line @typescript-eslint/no-misused-promises + async connect() { + const { services: { halEvents } } = await window.OpenProject.getPluginContext(); + + this.service = halEvents; + this.subscription = this.service.aggregated$('WorkPackage') + .pipe(filter((events) => events.some((event) => event.eventType === 'updated'))) + .subscribe(() => { this.refreshList(); }); + } + + disconnect() { + this.subscription?.unsubscribe(); + this.subscription = null; + this.service = null; + } + + private refreshList() { + this.listElement.src = this.listUrlValue; + } + + private get listElement() { + return this.element.querySelector('#backlogs_container')!; + } } diff --git a/modules/backlogs/app/controllers/rb_master_backlogs_controller.rb b/modules/backlogs/app/controllers/rb_master_backlogs_controller.rb index 9ee8e288269..dd6838e553b 100644 --- a/modules/backlogs/app/controllers/rb_master_backlogs_controller.rb +++ b/modules/backlogs/app/controllers/rb_master_backlogs_controller.rb @@ -33,25 +33,31 @@ class RbMasterBacklogsController < RbApplicationController menu_item :backlogs + before_action :load_backlogs, only: :index + def index - @owner_backlogs = Backlog.owner_backlogs(@project) - @sprint_backlogs = Backlog.sprint_backlogs(@project) + if turbo_frame_request? + render partial: "list", layout: false + else + render :index + end end - def split_view - @owner_backlogs = Backlog.owner_backlogs(@project) - @sprint_backlogs = Backlog.sprint_backlogs(@project) - - respond_to do |format| - format.html do - if turbo_frame_request? - render "work_packages/split_view", layout: false - else - render :index - end - end + def details + if turbo_frame_request? + render "work_packages/split_view", layout: false + else + load_backlogs + render :index end end def split_view_base_route = backlogs_project_backlogs_path(request.query_parameters) + + private + + def load_backlogs + @owner_backlogs = Backlog.owner_backlogs(@project) + @sprint_backlogs = Backlog.sprint_backlogs(@project) + end end diff --git a/modules/backlogs/app/views/rb_master_backlogs/_list.html.erb b/modules/backlogs/app/views/rb_master_backlogs/_list.html.erb new file mode 100644 index 00000000000..376cb8f1fa9 --- /dev/null +++ b/modules/backlogs/app/views/rb_master_backlogs/_list.html.erb @@ -0,0 +1,23 @@ + + <% if @owner_backlogs.empty? && @sprint_backlogs.empty? %> + <%= + render(Primer::Beta::Blankslate.new(border: true, spacious: true)) do |blankslate| + blankslate.with_visual_icon(icon: :versions) + blankslate.with_heading(tag: :h2).with_content(t(:backlogs_empty_title)) + + if current_user.allowed_in_project?(:manage_versions, @project) + blankslate.with_description_content(t(:backlogs_empty_action_text)) + end + end + %> + <% else %> +
    +
    + <%= render(Backlogs::BacklogComponent.with_collection(@sprint_backlogs, project: @project)) %> +
    +
    + <%= render(Backlogs::BacklogComponent.with_collection(@owner_backlogs, project: @project)) %> +
    +
    + <% end %> +
    diff --git a/modules/backlogs/app/views/rb_master_backlogs/index.html.erb b/modules/backlogs/app/views/rb_master_backlogs/index.html.erb index c4e99028ee1..5242488c321 100644 --- a/modules/backlogs/app/views/rb_master_backlogs/index.html.erb +++ b/modules/backlogs/app/views/rb_master_backlogs/index.html.erb @@ -29,6 +29,9 @@ See COPYRIGHT and LICENSE files for more details. <% html_title t(:label_backlogs) %> +<% content_controller "backlogs", + "backlogs-list-url-value": backlogs_project_backlogs_path(@project) %> + <% content_for :content_header do %> <%= render Primer::OpenProject::PageHeader.new do |header| @@ -54,32 +57,7 @@ See COPYRIGHT and LICENSE files for more details. <% end %> <% content_for :content_body do %> - <% if (@owner_backlogs.empty? && @sprint_backlogs.empty?) %> - <%= - render(Primer::Beta::Blankslate.new(border: true, spacious: true)) do |blankslate| - blankslate.with_visual_icon(icon: :versions) - blankslate.with_heading(tag: :h2).with_content(t(:backlogs_empty_title)) - - if current_user.allowed_in_project?(:manage_versions, @project) - blankslate.with_description_content(t(:backlogs_empty_action_text)) - end - end - %> - <% end %> - -
    -
    - -
    - <%= render(Backlogs::BacklogComponent.with_collection(@sprint_backlogs, project: @project)) %> -
    -
    - <%= render(Backlogs::BacklogComponent.with_collection(@owner_backlogs, project: @project)) %> -
    -
    -
    + <%= render partial: "list" %> <% end %> <% content_for :content_body_right do %> diff --git a/modules/backlogs/config/routes.rb b/modules/backlogs/config/routes.rb index ee43551ae7d..9aa4c7217ca 100644 --- a/modules/backlogs/config/routes.rb +++ b/modules/backlogs/config/routes.rb @@ -27,19 +27,15 @@ #++ Rails.application.routes.draw do - concern :with_split_view do |options| - get "details/:work_package_id(/:tab)", - action: options.fetch(:action, :split_view), - defaults: { tab: :overview }, - as: :details, - work_package_split_view: true - end - scope "", as: "backlogs" do scope "projects/:project_id", as: "project" do resources :backlogs, controller: :rb_master_backlogs, only: :index do collection do - concerns :with_split_view, base_route: :backlogs_project_backlogs_path + get "details/:work_package_id(/:tab)", + action: :details, + as: :details, + work_package_split_view: true, + defaults: { tab: :overview } end end diff --git a/modules/backlogs/lib/open_project/backlogs/engine.rb b/modules/backlogs/lib/open_project/backlogs/engine.rb index 81308d00175..a70ef5705f5 100644 --- a/modules/backlogs/lib/open_project/backlogs/engine.rb +++ b/modules/backlogs/lib/open_project/backlogs/engine.rb @@ -75,7 +75,7 @@ module OpenProject::Backlogs project_module :backlogs, dependencies: :work_package_tracking do # Master backlog permissions permission :view_master_backlog, - { rb_master_backlogs: %i[index split_view], + { rb_master_backlogs: %i[index details], rb_sprints: %i[index show show_name], rb_wikis: :show, rb_stories: %i[index show], diff --git a/modules/backlogs/spec/controllers/rb_master_backlogs_controller_spec.rb b/modules/backlogs/spec/controllers/rb_master_backlogs_controller_spec.rb index 595d6f32d70..992a47b9d59 100644 --- a/modules/backlogs/spec/controllers/rb_master_backlogs_controller_spec.rb +++ b/modules/backlogs/spec/controllers/rb_master_backlogs_controller_spec.rb @@ -48,16 +48,23 @@ RSpec.describe RbMasterBacklogsController do end describe "GET #index" do - it do + it "is successful" do get :index, params: { project_id: project.id } expect(response).to be_successful end + + it "assigns @owner_backlogs and @sprint_backlogs" do + get :index, params: { project_id: project.id } + + expect(assigns(:owner_backlogs)).to be_an(Array) + expect(assigns(:sprint_backlogs)).to be_an(Array) + end end - describe "GET #split_view" do - it do - get :split_view, params: { + describe "GET #details" do + it "is successful" do + get :details, params: { project_id: project.id, tab: :overview, work_package_id: story.id, @@ -66,5 +73,17 @@ RSpec.describe RbMasterBacklogsController do expect(response).to be_successful end + + it "assigns @owner_backlogs and @sprint_backlogs" do + get :details, params: { + project_id: project.id, + tab: :overview, + work_package_id: story.id, + work_package_split_view: true + } + + expect(assigns(:owner_backlogs)).to be_an(Array) + expect(assigns(:sprint_backlogs)).to be_an(Array) + end end end diff --git a/modules/backlogs/spec/routing/rb_master_backlogs_routing_spec.rb b/modules/backlogs/spec/routing/rb_master_backlogs_routing_spec.rb index b8ca73aab0a..4ee4440bccc 100644 --- a/modules/backlogs/spec/routing/rb_master_backlogs_routing_spec.rb +++ b/modules/backlogs/spec/routing/rb_master_backlogs_routing_spec.rb @@ -39,7 +39,7 @@ RSpec.describe RbMasterBacklogsController do it { expect(get("/projects/project_42/backlogs/details/33")).to route_to( controller: "rb_master_backlogs", - action: "split_view", + action: "details", project_id: "project_42", work_package_id: "33", tab: :overview, From 3458c7b1187129bdc09ae084390279eec835eb82 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 2 Feb 2026 19:24:25 -0300 Subject: [PATCH 078/293] Simplify DnD: remove empty list item CSS hiding --- .../assets/sass/backlogs/_master_backlog.sass | 6 ---- .../backlogs/backlog_component.html.erb | 32 ++++++++++--------- .../backlogs/backlog_component_spec.rb | 9 +----- 3 files changed, 18 insertions(+), 29 deletions(-) diff --git a/frontend/src/assets/sass/backlogs/_master_backlog.sass b/frontend/src/assets/sass/backlogs/_master_backlog.sass index 385f5e5c343..675cdba86b3 100644 --- a/frontend/src/assets/sass/backlogs/_master_backlog.sass +++ b/frontend/src/assets/sass/backlogs/_master_backlog.sass @@ -26,12 +26,6 @@ * See COPYRIGHT and LICENSE files for more details. ++ */ -li[data-empty-list-item] - display: none - -li[data-empty-list-item]:only-child - display: list-item - $op-backlogs-header--points-min-width: 5rem .op-backlogs-header diff --git a/modules/backlogs/app/components/backlogs/backlog_component.html.erb b/modules/backlogs/app/components/backlogs/backlog_component.html.erb index 05f5c412acb..6fbb014f602 100644 --- a/modules/backlogs/app/components/backlogs/backlog_component.html.erb +++ b/modules/backlogs/app/components/backlogs/backlog_component.html.erb @@ -32,21 +32,23 @@ See COPYRIGHT and LICENSE files for more details. <% border_box.with_header do %> <%= render(Backlogs::BacklogHeaderComponent.new(backlog:, project: @project, folded: folded?)) %> <% end %> - <% border_box.with_row(data: { empty_list_item: true }) do %> - <% if backlog.sprint_backlog? %> - <%= - render Primer::Beta::Blankslate.new(role: "status", aria: { live: "polite" }) do |blankslate| - blankslate.with_heading(tag: :h4).with_content(t(".sprint_backlog.blankslate_title")) - blankslate.with_description_content(t(".sprint_backlog.blankslate_description")) - end - %> - <% else %> - <%= - render Primer::Beta::Blankslate.new(role: "status", aria: { live: "polite" }) do |blankslate| - blankslate.with_heading(tag: :h4).with_content(t(".product_backlog.blankslate_title")) - blankslate.with_description_content(t(".product_backlog.blankslate_description")) - end - %> + <% if backlog.stories.empty? %> + <% border_box.with_row(data: { empty_list_item: true }) do %> + <% if backlog.sprint_backlog? %> + <%= + render Primer::Beta::Blankslate.new(role: "status", aria: { live: "polite" }) do |blankslate| + blankslate.with_heading(tag: :h4).with_content(t(".sprint_backlog.blankslate_title")) + blankslate.with_description_content(t(".sprint_backlog.blankslate_description")) + end + %> + <% else %> + <%= + render Primer::Beta::Blankslate.new(role: "status", aria: { live: "polite" }) do |blankslate| + blankslate.with_heading(tag: :h4).with_content(t(".product_backlog.blankslate_title")) + blankslate.with_description_content(t(".product_backlog.blankslate_description")) + end + %> + <% end %> <% end %> <% end %> <% backlog.stories.each do |story| %> diff --git a/modules/backlogs/spec/components/backlogs/backlog_component_spec.rb b/modules/backlogs/spec/components/backlogs/backlog_component_spec.rb index 52951ec51b8..f3bab7fb4a2 100644 --- a/modules/backlogs/spec/components/backlogs/backlog_component_spec.rb +++ b/modules/backlogs/spec/components/backlogs/backlog_component_spec.rb @@ -100,18 +100,11 @@ RSpec.describe Backlogs::BacklogComponent, type: :component do it "renders StoryComponent for each story" do render_component - expect(page).to have_css(".Box-row", count: 3) # 2 stories + 1 empty list item + expect(page).to have_css(".Box-row", count: 2) # 2 stories expect(page).to have_text(story1.subject) expect(page).to have_text(story2.subject) end - it "has the empty blankslate row with data attribute" do - render_component - - # The empty row has data-empty-list-item attribute - expect(page).to have_css("[data-empty-list-item]", visible: :all) - end - it "has drop target data attributes" do render_component From f61614eb614137b28f68a1f7026ba5a1fdec2182 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 2 Feb 2026 20:07:28 -0300 Subject: [PATCH 079/293] Update Controller flash messages --- config/locales/en.yml | 1 + .../app/controllers/rb_stories_controller.rb | 24 +++++++------------ .../controllers/rb_stories_controller_spec.rb | 1 - 3 files changed, 9 insertions(+), 17 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 9117b6f8d16..bba5778aafa 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -4129,6 +4129,7 @@ en: notice_successful_delete: "Successful deletion." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Successful update." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." diff --git a/modules/backlogs/app/controllers/rb_stories_controller.rb b/modules/backlogs/app/controllers/rb_stories_controller.rb index 76439dd4184..570222dbb22 100644 --- a/modules/backlogs/app/controllers/rb_stories_controller.rb +++ b/modules/backlogs/app/controllers/rb_stories_controller.rb @@ -70,14 +70,8 @@ class RbStoriesController < RbApplicationController # .new(user: current_user, story:) # .call(attributes: move_params) - if story.update(version_id: move_params[:target_id], **move_params.except(:target_id)) - render_success_flash_message_via_turbo_stream( - message: I18n.t(:enumeration_caption_order_changed) - ) - else - render_error_flash_message_via_turbo_stream( - message: I18n.t(:enumeration_could_not_be_moved) + call.errors.full_messages.to_sentence - ) + unless story.update(version_id: move_params[:target_id], **move_params.except(:target_id)) + render_error_flash_message_via_turbo_stream(message: I18n.t(:notice_unsuccessful_update)) #  TODO: display reason end backlog = Backlog.for(sprint: @sprint, project: @project) @@ -86,6 +80,10 @@ class RbStoriesController < RbApplicationController if story.saved_change_to_version_id? new_sprint = story.version.becomes(Sprint) new_backlog = Backlog.for(sprint: new_sprint, project: @project) + + render_success_flash_message_via_turbo_stream( + message: I18n.t(:notice_successful_move, from: @sprint.name, to: new_sprint.name) + ) replace_via_turbo_stream(component: Backlogs::BacklogComponent.new(backlog: new_backlog, project: @project)) end @@ -95,14 +93,8 @@ class RbStoriesController < RbApplicationController def reorder story = Story.find(params[:id]) - if story.update(move_to: reorder_param) - render_success_flash_message_via_turbo_stream( - message: I18n.t(:enumeration_caption_order_changed) - ) - else - render_error_flash_message_via_turbo_stream( - message: I18n.t(:enumeration_could_not_be_moved) + call.errors.full_messages.to_sentence - ) + unless story.update(move_to: reorder_param) + render_error_flash_message_via_turbo_stream(message: I18n.t(:notice_unsuccessful_update)) #  TODO: display reason end backlog = Backlog.for(sprint: @sprint, project: @project) diff --git a/modules/backlogs/spec/controllers/rb_stories_controller_spec.rb b/modules/backlogs/spec/controllers/rb_stories_controller_spec.rb index 096c4a0d608..541d09de965 100644 --- a/modules/backlogs/spec/controllers/rb_stories_controller_spec.rb +++ b/modules/backlogs/spec/controllers/rb_stories_controller_spec.rb @@ -86,7 +86,6 @@ RSpec.describe RbStoriesController do expect(response).to be_successful expect(response).to have_http_status :ok expect(response).to have_turbo_stream action: "replace", target: "backlogs-backlog-component-#{sprint.id}" - expect(response).to have_turbo_stream action: "flash", target: "op-primer-flash-component" end end From 82f908bd6dfe8f3958f307d52dd1ae8570bf21a7 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Tue, 3 Feb 2026 11:06:19 -0300 Subject: [PATCH 080/293] Ensure Edit sprint form takes full width --- frontend/src/assets/sass/backlogs/_master_backlog.sass | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/assets/sass/backlogs/_master_backlog.sass b/frontend/src/assets/sass/backlogs/_master_backlog.sass index 675cdba86b3..c05c7a83ba5 100644 --- a/frontend/src/assets/sass/backlogs/_master_backlog.sass +++ b/frontend/src/assets/sass/backlogs/_master_backlog.sass @@ -114,3 +114,7 @@ $op-backlogs-header--points-min-width: 5rem .FormControl-spacingWrapper flex-direction: row column-gap: 0.5rem + + & > :first-child + flex: 1 1 auto + min-width: 33% From bd0518288049442b1f5a0c87dd0862c42e918984 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Tue, 3 Feb 2026 15:31:13 -0300 Subject: [PATCH 081/293] Always show drag handle --- frontend/src/global_styles/primer/_overrides.sass | 9 --------- 1 file changed, 9 deletions(-) diff --git a/frontend/src/global_styles/primer/_overrides.sass b/frontend/src/global_styles/primer/_overrides.sass index c79dc6a82b4..77449bf785c 100644 --- a/frontend/src/global_styles/primer/_overrides.sass +++ b/frontend/src/global_styles/primer/_overrides.sass @@ -162,12 +162,3 @@ ul.SegmentedControl, .Box-row:is(.Box-row--draggable) padding-left: 0 - - .DragHandle - visibility: hidden - - &:hover, - &:focus-visible, - &:focus-within - .DragHandle - visibility: visible From ed7f504690bff7e57b9f5f13e5527318ea112a93 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Tue, 3 Feb 2026 15:39:59 -0300 Subject: [PATCH 082/293] Update backlog menu: wording (case), item order --- .../backlogs/backlog_menu_component.html.erb | 36 ++++++++++--------- modules/backlogs/config/locales/en.yml | 9 +++-- .../backlogs/backlog_menu_component_spec.rb | 24 ++++++------- .../features/backlogs/context_menu_spec.rb | 34 +++++++++--------- 4 files changed, 55 insertions(+), 48 deletions(-) diff --git a/modules/backlogs/app/components/backlogs/backlog_menu_component.html.erb b/modules/backlogs/app/components/backlogs/backlog_menu_component.html.erb index 416e57ffdad..520958c0ed1 100644 --- a/modules/backlogs/app/components/backlogs/backlog_menu_component.html.erb +++ b/modules/backlogs/app/components/backlogs/backlog_menu_component.html.erb @@ -44,13 +44,11 @@ See COPYRIGHT and LICENSE files for more details. ) do |item| item.with_leading_visual_icon(icon: :pencil) end - - menu.with_divider end if user_allowed?(:add_work_packages) menu.with_item( - label: t(:"backlogs.add_new_story"), + label: t(".action_menu.new_story"), href: new_project_work_packages_dialog_path( project, version_id: sprint.id, @@ -62,28 +60,22 @@ See COPYRIGHT and LICENSE files for more details. end end + if user_allowed?(:update_sprints) || user_allowed?(:add_work_packages) + menu.with_divider + end + menu.with_item( - label: t(:label_stories_tasks), + label: t(".action_menu.stories_tasks"), tag: :a, href: backlogs_project_sprint_query_path(project, sprint) ) do |item| item.with_leading_visual_icon(icon: :"op-view-list") end - if user_allowed?(:manage_versions) - menu.with_item( - label: t(:"backlogs.properties"), - tag: :a, - href: edit_version_path(sprint, back_url: backlogs_project_backlogs_path(project), project_id: project.id) - ) do |item| - item.with_leading_visual_icon(icon: :gear) - end - end - if backlog.sprint_backlog? if user_allowed?(:view_taskboards) menu.with_item( - label: t(:label_task_board), + label: t(".action_menu.task_board"), tag: :a, href: backlogs_project_sprint_taskboard_path(project, sprint) ) do |item| @@ -92,7 +84,7 @@ See COPYRIGHT and LICENSE files for more details. end menu.with_item( - label: t(:"backlogs.show_burndown_chart"), + label: t(".action_menu.burndown_chart"), tag: :a, href: backlogs_project_sprint_burndown_chart_path(project, sprint), disabled: !sprint.has_burndown? @@ -102,7 +94,7 @@ See COPYRIGHT and LICENSE files for more details. if project.module_enabled? "wiki" menu.with_item( - label: t(:label_wiki), + label: t(".action_menu.wiki"), tag: :a, href: edit_backlogs_project_sprint_wiki_path(project, sprint) ) do |item| @@ -110,5 +102,15 @@ See COPYRIGHT and LICENSE files for more details. end end end + + if user_allowed?(:manage_versions) + menu.with_item( + label: t(".action_menu.properties"), + tag: :a, + href: edit_version_path(sprint, back_url: backlogs_project_backlogs_path(project), project_id: project.id) + ) do |item| + item.with_leading_visual_icon(icon: :gear) + end + end end %> diff --git a/modules/backlogs/config/locales/en.yml b/modules/backlogs/config/locales/en.yml index 83f1013702e..fffbf10c753 100644 --- a/modules/backlogs/config/locales/en.yml +++ b/modules/backlogs/config/locales/en.yml @@ -57,7 +57,6 @@ en: task_type: "Task type" backlogs: - add_new_story: "New story" any: "any" backlog_settings: "Backlogs settings" burndown_graph: "Burndown Graph" @@ -79,7 +78,6 @@ en: other: "%{count} points" positions_could_not_be_rebuilt: "Positions could not be rebuilt." positions_rebuilt_successfully: "Positions rebuilt successfully." - properties: "Properties" rebuild: "Rebuild" rebuild_positions: "Rebuild positions" remaining_hours: "Remaining work" @@ -116,6 +114,12 @@ en: label_actions: "Backlog actions" action_menu: edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" story_component: label_drag_story: "Move %{name}" @@ -192,7 +196,6 @@ en: label_sprint_name: "Sprint \"%{name}\"" label_sprint_velocity: "Velocity %{velocity}, based on %{sprints} sprints with an average %{days} days" label_stories: "Stories" - label_stories_tasks: "Stories/Tasks" label_task_board: "Task board" label_version_setting: "Versions" label_version: 'Version' diff --git a/modules/backlogs/spec/components/backlogs/backlog_menu_component_spec.rb b/modules/backlogs/spec/components/backlogs/backlog_menu_component_spec.rb index 30f26b2ccf7..a5273fbf06d 100644 --- a/modules/backlogs/spec/components/backlogs/backlog_menu_component_spec.rb +++ b/modules/backlogs/spec/components/backlogs/backlog_menu_component_spec.rb @@ -87,7 +87,7 @@ RSpec.describe Backlogs::BacklogMenuComponent, type: :component do it "shows Add new story item with compose icon" do render_component - expect(page).to have_text(I18n.t(:"backlogs.add_new_story")) + expect(page).to have_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.new_story")) expect(page).to have_octicon(:compose) end end @@ -98,7 +98,7 @@ RSpec.describe Backlogs::BacklogMenuComponent, type: :component do it "does not show Add new story item" do render_component - expect(page).to have_no_text(I18n.t(:"backlogs.add_new_story")) + expect(page).to have_no_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.new_story")) end end @@ -108,7 +108,7 @@ RSpec.describe Backlogs::BacklogMenuComponent, type: :component do it "shows Properties item with gear icon" do render_component - expect(page).to have_text(I18n.t(:"backlogs.properties")) + expect(page).to have_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.properties")) expect(page).to have_octicon(:gear) end end @@ -119,7 +119,7 @@ RSpec.describe Backlogs::BacklogMenuComponent, type: :component do it "does not show Properties item" do render_component - expect(page).to have_no_text(I18n.t(:"backlogs.properties")) + expect(page).to have_no_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.properties")) end end @@ -129,7 +129,7 @@ RSpec.describe Backlogs::BacklogMenuComponent, type: :component do it "shows Task board item" do render_component - expect(page).to have_text(I18n.t(:label_task_board)) + expect(page).to have_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.task_board")) end end @@ -139,7 +139,7 @@ RSpec.describe Backlogs::BacklogMenuComponent, type: :component do it "does not show Task board item" do render_component - expect(page).to have_no_text(I18n.t(:label_task_board)) + expect(page).to have_no_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.task_board")) end end end @@ -150,13 +150,13 @@ RSpec.describe Backlogs::BacklogMenuComponent, type: :component do it "shows Stories/Tasks link" do render_component - expect(page).to have_text(I18n.t(:label_stories_tasks)) + expect(page).to have_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.stories_tasks")) end it "shows Burndown chart link" do render_component - expect(page).to have_text(I18n.t(:"backlogs.show_burndown_chart")) + expect(page).to have_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.burndown_chart")) end context "when sprint has no burndown (no dates)" do @@ -165,7 +165,7 @@ RSpec.describe Backlogs::BacklogMenuComponent, type: :component do it "shows Burndown chart link as disabled" do render_component - burndown_item = page.find("li", text: I18n.t(:"backlogs.show_burndown_chart")) + burndown_item = page.find("li", text: I18n.t(:"backlogs.backlog_menu_component.action_menu.burndown_chart")) expect(burndown_item[:class]).to include("ActionListItem--disabled") end end @@ -174,7 +174,7 @@ RSpec.describe Backlogs::BacklogMenuComponent, type: :component do it "shows Burndown chart link as enabled" do render_component - burndown_item = page.find("li", text: I18n.t(:"backlogs.show_burndown_chart")) + burndown_item = page.find("li", text: I18n.t(:"backlogs.backlog_menu_component.action_menu.burndown_chart")) expect(burndown_item[:class]).not_to include("ActionListItem--disabled") end end @@ -188,7 +188,7 @@ RSpec.describe Backlogs::BacklogMenuComponent, type: :component do it "shows Wiki item" do render_component - expect(page).to have_text(I18n.t(:label_wiki)) + expect(page).to have_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.wiki")) expect(page).to have_octicon(:book) end end @@ -200,7 +200,7 @@ RSpec.describe Backlogs::BacklogMenuComponent, type: :component do it "does not show Wiki item" do render_component - expect(page).to have_no_text(I18n.t(:label_wiki)) + expect(page).to have_no_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.wiki")) end end end diff --git a/modules/backlogs/spec/features/backlogs/context_menu_spec.rb b/modules/backlogs/spec/features/backlogs/context_menu_spec.rb index 85ac48a307c..f88bec9c30c 100644 --- a/modules/backlogs/spec/features/backlogs/context_menu_spec.rb +++ b/modules/backlogs/spec/features/backlogs/context_menu_spec.rb @@ -80,11 +80,12 @@ RSpec.describe "Backlogs context menu", :js do context "when the backlog is a sprint backlog (displayed on the left, the default)" do it "displays all menu entries" do within_backlog_context_menu do |menu| - expect(menu).to have_link I18n.t("backlogs.add_new_story") - expect(menu).to have_link I18n.t("label_stories_tasks") - expect(menu).to have_link I18n.t("label_task_board") - expect(menu).to have_link I18n.t("backlogs.show_burndown_chart") - expect(menu).to have_link I18n.t("label_wiki") + expect(menu).to have_selector :menuitem, count: 5 + expect(menu).to have_selector :menuitem, "New story" + expect(menu).to have_selector :menuitem, "Stories/Tasks" + expect(menu).to have_selector :menuitem, "Task board" + expect(menu).to have_selector :menuitem, "Burndown chart" + expect(menu).to have_selector :menuitem, "Wiki" end end end @@ -97,13 +98,14 @@ RSpec.describe "Backlogs context menu", :js do display: VersionSetting::DISPLAY_RIGHT) end - it 'only displays the "New story" and "Stories/Tasks" menu entries' do + it "only displays 4 menu entries" do within_backlog_context_menu do |menu| - expect(menu).to have_link I18n.t("backlogs.add_new_story") - expect(menu).to have_link I18n.t("label_stories_tasks") - expect(menu).to have_no_link I18n.t("label_task_board") - expect(menu).to have_no_link I18n.t("backlogs.show_burndown_chart") - expect(menu).to have_no_link I18n.t("label_wiki") + expect(menu).to have_selector :menuitem, count: 2 + expect(menu).to have_selector :menuitem, "New story" + expect(menu).to have_selector :menuitem, "Stories/Tasks" + expect(menu).to have_no_selector :menuitem, "Task board" + expect(menu).to have_no_selector :menuitem, "Burndown chart" + expect(menu).to have_no_selector :menuitem, "Wiki" end end end @@ -115,7 +117,7 @@ RSpec.describe "Backlogs context menu", :js do it 'disables the "Burndown chart" menu entry' do within_backlog_context_menu do |menu| - expect(menu).to have_link I18n.t("backlogs.show_burndown_chart", aria: { disabled: true }) + expect(menu).to have_selector :menuitem, "Burndown chart", disabled: true end end end @@ -127,7 +129,7 @@ RSpec.describe "Backlogs context menu", :js do it 'disables the "Burndown chart" menu entry' do within_backlog_context_menu do |menu| - expect(menu).to have_link I18n.t("backlogs.show_burndown_chart", aria: { disabled: true }) + expect(menu).to have_selector :menuitem, "Burndown chart", disabled: true end end end @@ -139,7 +141,7 @@ RSpec.describe "Backlogs context menu", :js do it 'does not display the "New story" menu entry' do within_backlog_context_menu do |menu| - expect(menu).to have_no_link I18n.t("backlogs.add_new_story") + expect(menu).to have_no_selector :menuitem, "New story" end end end @@ -151,7 +153,7 @@ RSpec.describe "Backlogs context menu", :js do it 'does not display the "Task board" menu entry' do within_backlog_context_menu do |menu| - expect(menu).to have_no_link I18n.t("label_task_board") + expect(menu).to have_no_selector :menuitem, "Task board" end end end @@ -163,7 +165,7 @@ RSpec.describe "Backlogs context menu", :js do it 'does not display the "Wiki" menu entry' do within_backlog_context_menu do |menu| - expect(menu).to have_no_link I18n.t("label_wiki") + expect(menu).to have_no_selector :menuitem, "Wiki" end end end From b1c67cc9b761ed99e9ba7993db3c7ba4885167bc Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Tue, 3 Feb 2026 16:37:04 -0300 Subject: [PATCH 083/293] Move 'Open details view' button out of story menu --- .../assets/sass/backlogs/_master_backlog.sass | 7 +++++-- .../components/backlogs/story_component.html.erb | 16 ++++++++++++++++ .../app/components/backlogs/story_component.rb | 3 ++- .../backlogs/story_menu_component.html.erb | 9 --------- .../components/backlogs/story_component_spec.rb | 7 +++++++ .../backlogs/story_menu_component_spec.rb | 7 ------- 6 files changed, 30 insertions(+), 19 deletions(-) diff --git a/frontend/src/assets/sass/backlogs/_master_backlog.sass b/frontend/src/assets/sass/backlogs/_master_backlog.sass index c05c7a83ba5..919ccd75c09 100644 --- a/frontend/src/assets/sass/backlogs/_master_backlog.sass +++ b/frontend/src/assets/sass/backlogs/_master_backlog.sass @@ -41,7 +41,7 @@ $op-backlogs-header--points-min-width: 5rem margin-left: var(--stack-gap-normal) .op-backlogs-header--menu - margin-left: var(--stack-gap-normal) + margin-left: calc(var(--stack-gap-normal) * 2 + var(--base-size-32)) .op-backlogs-collapsible display: flex @@ -82,7 +82,7 @@ $op-backlogs-header--points-min-width: 5rem display: grid grid-template-columns: var(--control-xsmall-size) 1fr minmax($op-backlogs-header--points-min-width, max-content) auto grid-template-rows: auto auto - grid-template-areas: "drag_handle info_line points menu" "drag_handle subject subject subject" + grid-template-areas: "drag_handle info_line points show_button menu" "drag_handle subject subject subject subject" align-items: center margin-bottom: var(--base-size-4) @@ -101,6 +101,9 @@ $op-backlogs-header--points-min-width: 5rem .op-backlogs-story--points margin-left: var(--stack-gap-normal) +.op-backlogs-story--show_button + margin-left: var(--stack-gap-normal) + .op-backlogs-story--menu margin-left: var(--stack-gap-normal) diff --git a/modules/backlogs/app/components/backlogs/story_component.html.erb b/modules/backlogs/app/components/backlogs/story_component.html.erb index a71589f147b..43d9477cf84 100644 --- a/modules/backlogs/app/components/backlogs/story_component.html.erb +++ b/modules/backlogs/app/components/backlogs/story_component.html.erb @@ -53,6 +53,22 @@ See COPYRIGHT and LICENSE files for more details. <% end %> <% end %> + <% grid.with_area(:show_button) do %> + <%= + render( + Primer::Beta::IconButton.new( + tag: :a, + scheme: :invisible, + icon: :"op-view-split", + "aria-label": t(:"js.button_open_details"), + href: details_backlogs_project_backlogs_path(project, story), + data: { turbo_frame: "content-bodyRight", turbo_action: "advance" }, + tooltip_direction: :se + ) + ) + %> + <% end %> + <% grid.with_area(:menu) do %> <%= render(Backlogs::StoryMenuComponent.new(story:, sprint:, max_position:)) %> <% end %> diff --git a/modules/backlogs/app/components/backlogs/story_component.rb b/modules/backlogs/app/components/backlogs/story_component.rb index 39c37f48a7f..c9fb2e4311d 100644 --- a/modules/backlogs/app/components/backlogs/story_component.rb +++ b/modules/backlogs/app/components/backlogs/story_component.rb @@ -32,13 +32,14 @@ module Backlogs class StoryComponent < ApplicationComponent include OpPrimer::ComponentHelpers - attr_reader :story, :sprint, :max_position, :current_user + attr_reader :story, :sprint, :project, :max_position, :current_user def initialize(story:, sprint:, max_position:, current_user: User.current) super() @story = story @sprint = sprint + @project = sprint.project @max_position = max_position @current_user = current_user end diff --git a/modules/backlogs/app/components/backlogs/story_menu_component.html.erb b/modules/backlogs/app/components/backlogs/story_menu_component.html.erb index 424bb34efbd..cfc4b4eb40e 100644 --- a/modules/backlogs/app/components/backlogs/story_menu_component.html.erb +++ b/modules/backlogs/app/components/backlogs/story_menu_component.html.erb @@ -36,15 +36,6 @@ See COPYRIGHT and LICENSE files for more details. tooltip_direction: :se ) - menu.with_item( - tag: :a, - label: t(:"js.button_open_details"), - href: details_backlogs_project_backlogs_path(project, story), - content_arguments: { turbo_frame: "content-bodyRight", turbo_action: "advance" } - ) do |item| - item.with_leading_visual_icon(icon: :"op-view-split") - end - menu.with_item( tag: :a, label: t(:"js.button_open_fullscreen"), diff --git a/modules/backlogs/spec/components/backlogs/story_component_spec.rb b/modules/backlogs/spec/components/backlogs/story_component_spec.rb index e09562c2701..ef289ebc9db 100644 --- a/modules/backlogs/spec/components/backlogs/story_component_spec.rb +++ b/modules/backlogs/spec/components/backlogs/story_component_spec.rb @@ -92,6 +92,13 @@ RSpec.describe Backlogs::StoryComponent, type: :component do expect(page).to have_text("5 points") end + it "shows Open details link (split view)" do + render_component + + expect(page).to have_text(I18n.t(:"js.button_open_details")) + expect(page).to have_octicon(:"op-view-split") + end + it "renders StoryMenuComponent" do render_component diff --git a/modules/backlogs/spec/components/backlogs/story_menu_component_spec.rb b/modules/backlogs/spec/components/backlogs/story_menu_component_spec.rb index 7a77d94a9a9..21daaba4a06 100644 --- a/modules/backlogs/spec/components/backlogs/story_menu_component_spec.rb +++ b/modules/backlogs/spec/components/backlogs/story_menu_component_spec.rb @@ -66,13 +66,6 @@ RSpec.describe Backlogs::StoryMenuComponent, type: :component do end describe "standard items" do - it "shows Open details link (split view)" do - render_component - - expect(page).to have_text(I18n.t(:"js.button_open_details")) - expect(page).to have_octicon(:"op-view-split") - end - it "shows Open fullscreen link (full page)" do render_component From 5ad7d1d31533c28336328f37e9950382022071f6 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Tue, 3 Feb 2026 19:58:04 -0300 Subject: [PATCH 084/293] Update "No versions defined" Blankslate text --- modules/backlogs/config/locales/en.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/backlogs/config/locales/en.yml b/modules/backlogs/config/locales/en.yml index fffbf10c753..fa5cbf7ead0 100644 --- a/modules/backlogs/config/locales/en.yml +++ b/modules/backlogs/config/locales/en.yml @@ -145,8 +145,8 @@ en: backlogs_velocity_missing: "No velocity could be calculated for this project" backlogs_velocity_varies: "Velocity varies significantly over sprints" backlogs_wiki_template: "Template for sprint wiki page" - backlogs_empty_title: "No versions are defined to be used in backlogs" - backlogs_empty_action_text: "To get started with backlogs, create a new version and assign it to a backlogs column." + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" burndown: story_points: "Story points" From 03f3eb461a007175875ccc2cffb38dc72b769f19 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Tue, 3 Feb 2026 22:14:31 -0300 Subject: [PATCH 085/293] Remove unused backlogs translation keys Co-Authored-By: Claude Opus 4.5 --- modules/backlogs/config/locales/en.yml | 71 -------------------------- 1 file changed, 71 deletions(-) diff --git a/modules/backlogs/config/locales/en.yml b/modules/backlogs/config/locales/en.yml index fa5cbf7ead0..7183466ffd3 100644 --- a/modules/backlogs/config/locales/en.yml +++ b/modules/backlogs/config/locales/en.yml @@ -58,16 +58,8 @@ en: backlogs: any: "any" - backlog_settings: "Backlogs settings" - burndown_graph: "Burndown Graph" - card_paper_size: "Paper size for card printing" - chart_options: "Chart options" - close: "Close" column_width: "Column width" - date: "Day" definition_of_done: "Definition of Done" - generating_chart: "Generating Graph..." - hours: "Hours" impediment: "Impediment" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." @@ -81,7 +73,6 @@ en: rebuild: "Rebuild" rebuild_positions: "Rebuild positions" remaining_hours: "Remaining work" - remaining_hours_ideal: "Remaining work (ideal)" show_burndown_chart: "Burndown Chart" story: "Story" story_points: @@ -93,7 +84,6 @@ en: user_preference: header_backlogs: "Backlogs module" button_update_backlogs: "Update backlogs module" - x_more: "%{count} more..." backlog_component: sprint_backlog: @@ -127,23 +117,12 @@ en: story_menu_component: label_actions: "Story actions" - backlogs_active: "active" - backlogs_any: "any" - backlogs_inactive: "Project shows no activity" backlogs_points_burn_direction: "Points burn up/down" backlogs_product_backlog: "Product backlog" - backlogs_product_backlog_unsized: "The top of the product backlog has unsized stories" - backlogs_sizing_inconsistent: "Story sizes vary against their estimates" - backlogs_sprint_notes_missing: "Closed sprints without retrospective/review notes" - backlogs_sprint_unestimated: "Closed or active sprints with unestimated stories" - backlogs_sprint_unsized: "Project has stories on active or recently closed sprints that were not sized" - backlogs_sprints: "Sprints" backlogs_story: "Story" backlogs_story_type: "Story types" backlogs_task: "Task" backlogs_task_type: "Task type" - backlogs_velocity_missing: "No velocity could be calculated for this project" - backlogs_velocity_varies: "Velocity varies significantly over sprints" backlogs_wiki_template: "Template for sprint wiki page" backlogs_empty_title: "No versions are defined yet" backlogs_empty_action_text: "To start using backlogs, please create a version first" @@ -152,67 +131,29 @@ en: story_points: "Story points" story_points_ideal: "Story points (ideal)" - button_edit_wiki: "Edit wiki page" - errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "The following errors were encountered:" - error_intro_singular: "The following error was encountered:" - error_outro: "Please correct the above errors before submitting again." - - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - - ideal: "ideal" - - inclusion: "is not included in the list" - - label_back_to_project: "Back to project page" - label_backlog: "Backlog" label_backlogs: "Backlogs" label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "IDs of blocked work packages" - label_burndown: "Burndown" label_column_in_backlog: "Column in backlog" - label_hours: "hours" - label_work_package_hierarchy: "Work package Hierarchy" - label_master_backlog: "Master Backlog" - label_not_prioritized: "not prioritized" - label_points: "points" label_points_burn_down: "Down" label_points_burn_up: "Up" - label_product_backlog: "product backlog" - label_select_all: "Select all" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "sprint backlog" - label_sprint_cards: "Export cards" label_sprint_impediments: "Sprint Impediments" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Velocity %{velocity}, based on %{sprints} sprints with an average %{days} days" - label_stories: "Stories" label_task_board: "Task board" - label_version_setting: "Versions" - label_version: 'Version' - label_webcal: "Webcal Feed" - label_wiki: "Wiki" permission_view_master_backlog: "View master backlog" permission_view_taskboards: "View taskboards" permission_select_done_statuses: "Select done statuses" permission_update_sprints: "Update sprints" - points_accepted: "points accepted" - points_committed: "points committed" - points_resolved: "points resolved" - points_to_accept: "points not accepted" - points_to_resolve: "points not resolved" - project_module_backlogs: "Backlogs" rb_burndown_charts: @@ -220,20 +161,8 @@ en: blankslate_title: "No burndown data available" blankslate_description: "Set start and end date for the sprint to generate a burndown chart." - rb_label_copy_tasks: "Copy work packages" - rb_label_copy_tasks_all: "All" - rb_label_copy_tasks_none: "None" - rb_label_copy_tasks_open: "Open" - rb_label_link_to_original: "Include link to original story" - remaining_hours: "remaining work" - required_burn_rate_hours: "required burn rate (hours)" - required_burn_rate_points: "required burn rate (points)" - - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" - version_settings_display_label: "Column in backlog" version_settings_display_option_left: "left" version_settings_display_option_none: "none" From 51a0a8b35b0e81238cfce3394677d51723f46dad Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Wed, 4 Feb 2026 20:23:10 +0200 Subject: [PATCH 086/293] Use the Stories::Update service for moving and reordering stories. --- .../work_packages/set_attributes_service.rb | 2 +- .../app/controllers/rb_stories_controller.rb | 21 ++++++++++++------- .../app/services/stories/update_service.rb | 9 ++------ .../backlogs/patches/base_contract_patch.rb | 1 + 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/app/services/work_packages/set_attributes_service.rb b/app/services/work_packages/set_attributes_service.rb index b0c40bef6df..51b59647620 100644 --- a/app/services/work_packages/set_attributes_service.rb +++ b/app/services/work_packages/set_attributes_service.rb @@ -69,7 +69,7 @@ class WorkPackages::SetAttributesService < BaseServices::SetAttributes def set_static_attributes(attributes) assignable_attributes = attributes.select do |key, _| - !CustomField.custom_field_attribute?(key) && work_package.respond_to?(key) + !CustomField.custom_field_attribute?(key) && work_package.respond_to?("#{key}=") end work_package.attributes = assignable_attributes diff --git a/modules/backlogs/app/controllers/rb_stories_controller.rb b/modules/backlogs/app/controllers/rb_stories_controller.rb index 570222dbb22..01d0288a406 100644 --- a/modules/backlogs/app/controllers/rb_stories_controller.rb +++ b/modules/backlogs/app/controllers/rb_stories_controller.rb @@ -66,12 +66,15 @@ class RbStoriesController < RbApplicationController def move story = Story.find(params[:id]) - # call = Stories::UpdateService - # .new(user: current_user, story:) - # .call(attributes: move_params) + call = Stories::UpdateService + .new(user: current_user, story:) + .call(attributes: { + version_id: move_params[:target_id], + position: move_params[:position] + }) - unless story.update(version_id: move_params[:target_id], **move_params.except(:target_id)) - render_error_flash_message_via_turbo_stream(message: I18n.t(:notice_unsuccessful_update)) #  TODO: display reason + unless call.success? + render_error_flash_message_via_turbo_stream(message: I18n.t(:notice_unsuccessful_update)) # TODO: display reason end backlog = Backlog.for(sprint: @sprint, project: @project) @@ -93,8 +96,12 @@ class RbStoriesController < RbApplicationController def reorder story = Story.find(params[:id]) - unless story.update(move_to: reorder_param) - render_error_flash_message_via_turbo_stream(message: I18n.t(:notice_unsuccessful_update)) #  TODO: display reason + call = Stories::UpdateService + .new(user: current_user, story:) + .call(attributes: { move_to: reorder_param }) + + unless call.success? + render_error_flash_message_via_turbo_stream(message: I18n.t(:notice_unsuccessful_update)) # TODO: display reason end backlog = Backlog.for(sprint: @sprint, project: @project) diff --git a/modules/backlogs/app/services/stories/update_service.rb b/modules/backlogs/app/services/stories/update_service.rb index 02f4862078f..5f29ceed5b5 100644 --- a/modules/backlogs/app/services/stories/update_service.rb +++ b/modules/backlogs/app/services/stories/update_service.rb @@ -34,16 +34,11 @@ class Stories::UpdateService self.story = story end - def call(attributes: {}, prev: nil) + def call(attributes: {}) create_call = WorkPackages::UpdateService .new(user:, model: story) - .call(**attributes.symbolize_keys) - - if create_call.success? && prev - create_call.result.move_after prev - end - + .call(**attributes.to_h.symbolize_keys) create_call end end diff --git a/modules/backlogs/lib/open_project/backlogs/patches/base_contract_patch.rb b/modules/backlogs/lib/open_project/backlogs/patches/base_contract_patch.rb index 0da6bb57435..70eb1d3aadb 100644 --- a/modules/backlogs/lib/open_project/backlogs/patches/base_contract_patch.rb +++ b/modules/backlogs/lib/open_project/backlogs/patches/base_contract_patch.rb @@ -31,5 +31,6 @@ module OpenProject::Backlogs::Patches::BaseContractPatch included do attribute :story_points + attribute :position end end From 099bc5a15ae684e4f0435a5749659eaba2d2260e Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Wed, 4 Feb 2026 21:19:57 +0200 Subject: [PATCH 087/293] Keep the Stories::UpdateService prev argument for the stories controller update action. --- modules/backlogs/app/services/stories/update_service.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/backlogs/app/services/stories/update_service.rb b/modules/backlogs/app/services/stories/update_service.rb index 5f29ceed5b5..fb835a7dbcc 100644 --- a/modules/backlogs/app/services/stories/update_service.rb +++ b/modules/backlogs/app/services/stories/update_service.rb @@ -34,11 +34,16 @@ class Stories::UpdateService self.story = story end - def call(attributes: {}) + def call(attributes: {}, prev: nil) create_call = WorkPackages::UpdateService .new(user:, model: story) .call(**attributes.to_h.symbolize_keys) + + if create_call.success? + create_call.result.move_after prev + end + create_call end end From dc9d9cb68003a8de6c1bcc9484e756088d786d95 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Wed, 4 Feb 2026 15:59:15 -0300 Subject: [PATCH 088/293] Visually highlight selected story, set aria-current Uses URL for state / as source-of-truth. Changes highlight and selected color to blue, focus color to grey. --- .../src/global_styles/primer/_overrides.sass | 4 +++ .../dynamic/backlogs.controller.ts | 30 ++++++++++++++++++- .../dynamic/backlogs/story.controller.ts | 15 ++++++++++ .../backlogs/backlog_component.html.erb | 7 +++-- .../views/rb_master_backlogs/index.html.erb | 3 +- 5 files changed, 55 insertions(+), 4 deletions(-) diff --git a/frontend/src/global_styles/primer/_overrides.sass b/frontend/src/global_styles/primer/_overrides.sass index 77449bf785c..0ca650eb626 100644 --- a/frontend/src/global_styles/primer/_overrides.sass +++ b/frontend/src/global_styles/primer/_overrides.sass @@ -153,6 +153,10 @@ ul.SegmentedControl, .ActionListItem-label[class^="__hl_"], .ActionListItem-label[class*=" __hl_"] color: var(--control-fgColor-disabled) !important +.Box-row--focus-gray + &:focus-visible + background-color: var(--bgColor-muted) + .Box-row--focus-blue &:focus-visible background-color: var(--bgColor-accent-muted) diff --git a/frontend/src/stimulus/controllers/dynamic/backlogs.controller.ts b/frontend/src/stimulus/controllers/dynamic/backlogs.controller.ts index 768dcb9a258..8837db871b4 100644 --- a/frontend/src/stimulus/controllers/dynamic/backlogs.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/backlogs.controller.ts @@ -27,21 +27,32 @@ //++ import { Controller } from '@hotwired/stimulus'; -import { FrameElement } from '@hotwired/turbo'; +import { FrameElement, TurboVisitEvent } from '@hotwired/turbo'; import { HalEventsService } from 'core-app/features/hal/services/hal-events.service'; import { filter, Subscription } from 'rxjs'; +import StoryController from './backlogs/story.controller'; export default class BacklogsController extends Controller { + static outlets = ['backlogs--story']; + declare backlogsStoryOutlets:StoryController[]; + static values = { listUrl: String, }; declare listUrlValue:string; + + private abortController:AbortController|null = null; private service:HalEventsService|null = null; private subscription:Subscription|null = null; // eslint-disable-next-line @typescript-eslint/no-misused-promises async connect() { + this.abortController = new AbortController(); + const { signal } = this.abortController; + + document.addEventListener('turbo:visit', this.updateSelection, { signal }); + const { services: { halEvents } } = await window.OpenProject.getPluginContext(); this.service = halEvents; @@ -54,8 +65,25 @@ export default class BacklogsController extends Controller { this.subscription?.unsubscribe(); this.subscription = null; this.service = null; + + this.abortController?.abort(); + this.abortController = null; } + private updateSelection = (event:TurboVisitEvent) => { + const url = new URL(event.detail.url, window.location.origin); + const match = /\/details\/(\d+)/.exec(url.pathname); + const selectedId = match ? Number(match[1]) : null; + + this.backlogsStoryOutlets.forEach((story) => { + if (selectedId !== null && story.idValue === selectedId) { + story.markAsSelected(event); + } else { + story.unmarkAsSelected(event); + } + }); + }; + private refreshList() { this.listElement.src = this.listUrlValue; } diff --git a/frontend/src/stimulus/controllers/dynamic/backlogs/story.controller.ts b/frontend/src/stimulus/controllers/dynamic/backlogs/story.controller.ts index 9340149f9fe..e9eb3aad673 100644 --- a/frontend/src/stimulus/controllers/dynamic/backlogs/story.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/backlogs/story.controller.ts @@ -31,13 +31,18 @@ import * as Turbo from '@hotwired/turbo'; export default class StoryController extends Controller implements EventListenerObject { static values = { + id: Number, splitUrl: String, fullUrl: String, }; + declare idValue:number; declare splitUrlValue:string; declare fullUrlValue:string; + static classes = ['selected']; + declare readonly selectedClass:string; + private abortController:AbortController|null = null; private clickTimeout:number|null = null; @@ -60,6 +65,16 @@ export default class StoryController extends Controller implements } } + markAsSelected(_event:Event) { + this.element.classList.add(this.selectedClass); + this.element.setAttribute('aria-current', 'true'); + } + + unmarkAsSelected(_event:Event) { + this.element.classList.remove(this.selectedClass); + this.element.removeAttribute('aria-current'); + } + handleEvent(event:Event):void { switch (event.type) { case 'click': diff --git a/modules/backlogs/app/components/backlogs/backlog_component.html.erb b/modules/backlogs/app/components/backlogs/backlog_component.html.erb index 6fbb014f602..9ee1cf8577f 100644 --- a/modules/backlogs/app/components/backlogs/backlog_component.html.erb +++ b/modules/backlogs/app/components/backlogs/backlog_component.html.erb @@ -54,11 +54,14 @@ See COPYRIGHT and LICENSE files for more details. <% backlog.stories.each do |story| %> <% border_box.with_row( id: dom_id(story), - classes: "Box-row--hover-gray Box-row--focus-blue Box-row--clickable Box-row--draggable", + classes: "Box-row--hover-blue Box-row--focus-gray Box-row--clickable Box-row--draggable", data: draggable_item_config(story).merge( + story: true, controller: "backlogs--story", + backlogs__story_id_value: story.id, backlogs__story_split_url_value: details_backlogs_project_backlogs_path(project, story), - backlogs__story_full_url_value: work_package_path(story) + backlogs__story_full_url_value: work_package_path(story), + backlogs__story_selected_class: "Box-row--blue" ), tabindex: 0 ) do %> diff --git a/modules/backlogs/app/views/rb_master_backlogs/index.html.erb b/modules/backlogs/app/views/rb_master_backlogs/index.html.erb index 5242488c321..5f87d6dfd1b 100644 --- a/modules/backlogs/app/views/rb_master_backlogs/index.html.erb +++ b/modules/backlogs/app/views/rb_master_backlogs/index.html.erb @@ -30,7 +30,8 @@ See COPYRIGHT and LICENSE files for more details. <% html_title t(:label_backlogs) %> <% content_controller "backlogs", - "backlogs-list-url-value": backlogs_project_backlogs_path(@project) %> + "backlogs-list-url-value": backlogs_project_backlogs_path(@project), + "backlogs-backlogs--story-outlet": "li[data-story]" %> <% content_for :content_header do %> <%= From b7bfbdded53d502e11aa25f241110c476feaa8c0 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Wed, 4 Feb 2026 20:32:42 -0300 Subject: [PATCH 089/293] Remove unused story create/update actions The new Primer-based backlogs UI uses the split view for story editing instead of inline editing. This removes the legacy create/update actions, routes, permissions, and associated frontend code. Co-Authored-By: Claude Opus 4.5 --- .../controllers/dynamic/backlogs/story.ts | 145 ------------------ .../backlogs/taskboard-legacy.controller.ts | 1 - .../app/controllers/rb_stories_controller.rb | 43 ------ .../app/views/shared/_server_variables.js.erb | 3 - modules/backlogs/config/routes.rb | 2 +- .../lib/open_project/backlogs/engine.rb | 6 - .../controllers/rb_stories_controller_spec.rb | 20 --- .../spec/routing/rb_stories_routing_spec.rb | 15 -- 8 files changed, 1 insertion(+), 234 deletions(-) delete mode 100644 frontend/src/stimulus/controllers/dynamic/backlogs/story.ts diff --git a/frontend/src/stimulus/controllers/dynamic/backlogs/story.ts b/frontend/src/stimulus/controllers/dynamic/backlogs/story.ts deleted file mode 100644 index a9f945adacf..00000000000 --- a/frontend/src/stimulus/controllers/dynamic/backlogs/story.ts +++ /dev/null @@ -1,145 +0,0 @@ -//-- copyright -// OpenProject is an open source project management software. -// Copyright (C) the OpenProject GmbH -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License version 3. -// -// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: -// Copyright (C) 2006-2013 Jean-Philippe Lang -// Copyright (C) 2010-2013 the ChiliProject Team -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -// -// See COPYRIGHT and LICENSE files for more details. -//++ - -import { FetchResponse } from '@rails/request.js'; - -/************************************** - STORY -***************************************/ -// @ts-expect-error TS(2304): Cannot find name 'RB'. -RB.Story = (function ($) { - // @ts-expect-error TS(2304): Cannot find name 'RB'. - return RB.Object.create(RB.WorkPackage, RB.EditableInplace, { - initialize(el:any) { - this.$ = $(el); - this.el = el; - - // Associate this object with the element for later retrieval - this.$.data('this', this); - this.$.on('click', '.editable', this.handleClick); - }, - - /** - * Callbacks from model.js - **/ - beforeSave() { - this.refreshStory(); - }, - - afterCreate(data:string, response:FetchResponse) { - this.refreshStory(); - }, - - afterUpdate(data:string, response:FetchResponse) { - this.refreshStory(); - }, - - refreshed() { - this.refreshStory(); - }, - /**/ - - editDialogTitle() { - return `Story #${this.getID()}`; - }, - - editorDisplayed(editor:any) { }, - - getPoints() { - const points = parseInt(this.$.find('.story_points').first().text(), 10); - return isNaN(points) ? 0 : points; - }, - - getType() { - return 'Story'; - }, - - markIfClosed() { - // Do nothing - }, - - newDialogTitle() { - return 'New Story'; - }, - - refreshStory() { - this.recalcVelocity(); - }, - - recalcVelocity() { - this.$.parents('.backlog').first().data('this').refresh(); - }, - - saveDirectives() { - let url; - let prev; - let sprintId; - - let data; - let method; - - prev = this.$.prev(); - sprintId = this.$.parents('.backlog').data('this').isSprintBacklog() - ? this.$.parents('.backlog').data('this').getSprint().data('this') -.getID() - : ''; - - data = `prev=${ - prev.length === 1 ? prev.data('this').getID() : '' - }&version_id=${sprintId}`; - - if (this.$.find('.editor').length > 0) { - data += `&${this.$.find('.editor').serialize()}`; - } - - //TODO: this might be unsave in case the parent of this story is not the - // sprint backlog, then we dont have a sprintId an cannot generate a - // valid url - one option might be to take RB.constants.sprint_id - // hoping it exists - if (this.isNew()) { - // @ts-expect-error TS(2304): Cannot find name 'RB'. - url = RB.urlFor('create_story', { sprint_id: sprintId }); - method = 'post'; - } else { - // @ts-expect-error TS(2304): Cannot find name 'RB'. - url = RB.urlFor('update_story', { id: this.getID(), sprint_id: sprintId }); - method = 'put'; - } - - return { - url, - method, - data, - }; - }, - - beforeSaveDragResult() { - // Do nothing - }, - }); -}(jQuery)); diff --git a/frontend/src/stimulus/controllers/dynamic/backlogs/taskboard-legacy.controller.ts b/frontend/src/stimulus/controllers/dynamic/backlogs/taskboard-legacy.controller.ts index ed66caeff51..3e3beebea8e 100644 --- a/frontend/src/stimulus/controllers/dynamic/backlogs/taskboard-legacy.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/backlogs/taskboard-legacy.controller.ts @@ -8,7 +8,6 @@ import './model'; import './editable_inplace'; import './sprint'; import './work_package'; -import './story'; import './task'; import './impediment'; import './taskboard'; diff --git a/modules/backlogs/app/controllers/rb_stories_controller.rb b/modules/backlogs/app/controllers/rb_stories_controller.rb index 01d0288a406..486e4eb24e5 100644 --- a/modules/backlogs/app/controllers/rb_stories_controller.rb +++ b/modules/backlogs/app/controllers/rb_stories_controller.rb @@ -31,38 +31,6 @@ class RbStoriesController < RbApplicationController include OpTurbo::ComponentStream - # This is a constant here because we will recruit it elsewhere to whitelist - # attributes. This is necessary for now as we still directly use `attributes=` - # in non-controller code. - PERMITTED_PARAMS = %i[id status_id version_id - story_points type_id subject author_id - sprint_id].freeze - - def create - call = Stories::CreateService - .new(user: current_user) - .call(attributes: story_params, - prev: params[:prev]) - - respond_with_story(call) - end - - def update - story = Story.find(params[:id]) - - call = Stories::UpdateService - .new(user: current_user, story:) - .call(attributes: story_params, - prev: params[:prev]) - - unless call.success? - # reload the story to be able to display it correctly - call.result.reload - end - - respond_with_story(call) - end - def move story = Story.find(params[:id]) @@ -113,13 +81,6 @@ class RbStoriesController < RbApplicationController private - def respond_with_story(call) - status = call.success? ? 200 : 400 - story = call.result - - respond_with_turbo_streams - end - def move_params params.require(%i[position target_id]) params.permit(:position, :target_id) @@ -128,8 +89,4 @@ class RbStoriesController < RbApplicationController def reorder_param params.expect(:direction) end - - def story_params - params.permit(PERMITTED_PARAMS).merge(project: @project).to_h - end end diff --git a/modules/backlogs/app/views/shared/_server_variables.js.erb b/modules/backlogs/app/views/shared/_server_variables.js.erb index c50138cfda9..06d9390d586 100644 --- a/modules/backlogs/app/views/shared/_server_variables.js.erb +++ b/modules/backlogs/app/views/shared/_server_variables.js.erb @@ -40,9 +40,6 @@ RB.urlFor = (function () { const routes = { update_sprint: '<%= backlogs_project_sprint_path(project_id: @project.identifier, id: ":id") %>', - create_story: '<%= backlogs_project_sprint_stories_path(project_id: @project.identifier, sprint_id: ":sprint_id") %>', - update_story: '<%= backlogs_project_sprint_story_path(project_id: @project.identifier, sprint_id: ":sprint_id", id: ":id") %>', - create_task: '<%= backlogs_project_sprint_tasks_path(project_id: @project.identifier, sprint_id: ":sprint_id") %>', update_task: '<%= backlogs_project_sprint_task_path(project_id: @project.identifier, sprint_id: ":sprint_id", id: ":id") %>', diff --git a/modules/backlogs/config/routes.rb b/modules/backlogs/config/routes.rb index 9aa4c7217ca..ee9d1aba906 100644 --- a/modules/backlogs/config/routes.rb +++ b/modules/backlogs/config/routes.rb @@ -52,7 +52,7 @@ Rails.application.routes.draw do resources :tasks, controller: :rb_tasks, only: %i[create update] - resources :stories, controller: :rb_stories, only: %i[create update] do + resources :stories, controller: :rb_stories, only: [] do member do put :move post :reorder diff --git a/modules/backlogs/lib/open_project/backlogs/engine.rb b/modules/backlogs/lib/open_project/backlogs/engine.rb index a70ef5705f5..18210c6e1e0 100644 --- a/modules/backlogs/lib/open_project/backlogs/engine.rb +++ b/modules/backlogs/lib/open_project/backlogs/engine.rb @@ -54,7 +54,6 @@ module OpenProject::Backlogs settings:) do Rails.application.reloader.to_prepare do OpenProject::AccessControl.permission(:add_work_packages).tap do |add| - add.controller_actions << "rb_stories/create" add.controller_actions << "rb_tasks/create" add.controller_actions << "rb_impediments/create" end @@ -62,14 +61,9 @@ module OpenProject::Backlogs OpenProject::AccessControl.permission(:edit_work_packages).tap do |edit| edit.controller_actions << "rb_stories/move" edit.controller_actions << "rb_stories/reorder" - edit.controller_actions << "rb_stories/update" edit.controller_actions << "rb_tasks/update" edit.controller_actions << "rb_impediments/update" end - - OpenProject::AccessControl.permission(:change_work_package_status).tap do |edit| - edit.controller_actions << "rb_stories/update" - end end project_module :backlogs, dependencies: :work_package_tracking do diff --git a/modules/backlogs/spec/controllers/rb_stories_controller_spec.rb b/modules/backlogs/spec/controllers/rb_stories_controller_spec.rb index 541d09de965..ab73f04b839 100644 --- a/modules/backlogs/spec/controllers/rb_stories_controller_spec.rb +++ b/modules/backlogs/spec/controllers/rb_stories_controller_spec.rb @@ -47,16 +47,6 @@ RSpec.describe RbStoriesController do .and_return({ "story_types" => [type_feature.id], "task_type" => type_task.id }) end - describe "POST #create" do - it "responds with success", :aggregate_failures do - post :create, params: { project_id: project.id, sprint_id: sprint.id }, - format: :turbo_stream - - expect(response).to be_successful - expect(response).to have_http_status :ok - end - end - describe "PUT #move" do let(:other_sprint) { create(:sprint, name: "Sprint 2", project:) } @@ -88,14 +78,4 @@ RSpec.describe RbStoriesController do expect(response).to have_turbo_stream action: "replace", target: "backlogs-backlog-component-#{sprint.id}" end end - - describe "PATCH #update" do - it "responds with success", :aggregate_failures do - patch :update, params: { project_id: project.id, sprint_id: sprint.id, id: story.id }, - format: :turbo_stream - - expect(response).to be_successful - expect(response).to have_http_status :ok - end - end end diff --git a/modules/backlogs/spec/routing/rb_stories_routing_spec.rb b/modules/backlogs/spec/routing/rb_stories_routing_spec.rb index c6dcb09166b..d0af15bece9 100644 --- a/modules/backlogs/spec/routing/rb_stories_routing_spec.rb +++ b/modules/backlogs/spec/routing/rb_stories_routing_spec.rb @@ -30,13 +30,6 @@ require "spec_helper" RSpec.describe RbStoriesController do describe "routing" do - it { - expect(post("/projects/project_42/sprints/21/stories")).to route_to(controller: "rb_stories", - action: "create", - project_id: "project_42", - sprint_id: "21") - } - it { expect(put("/projects/project_42/sprints/21/stories/85/move")).to route_to( controller: "rb_stories", @@ -56,13 +49,5 @@ RSpec.describe RbStoriesController do id: "85" ) } - - it { - expect(put("/projects/project_42/sprints/21/stories/85")).to route_to(controller: "rb_stories", - action: "update", - project_id: "project_42", - sprint_id: "21", - id: "85") - } end end From 4b3fb24e2718cdc3c3678670e46c71490ea8265e Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Wed, 4 Feb 2026 20:44:09 -0300 Subject: [PATCH 090/293] Use outlet callback for initial selection When story outlets connect, check current URL and mark as selected if the URL matches. This ensures proper selection state on initial page load or direct navigation to a /details/{id} URL. Co-Authored-By: Claude Opus 4.5 --- .../controllers/dynamic/backlogs.controller.ts | 15 +++++++++++++-- .../dynamic/backlogs/story.controller.ts | 4 ++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/frontend/src/stimulus/controllers/dynamic/backlogs.controller.ts b/frontend/src/stimulus/controllers/dynamic/backlogs.controller.ts index 8837db871b4..239409703ef 100644 --- a/frontend/src/stimulus/controllers/dynamic/backlogs.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/backlogs.controller.ts @@ -70,10 +70,16 @@ export default class BacklogsController extends Controller { this.abortController = null; } + backlogsStoryOutletConnected(outlet:StoryController) { + const selectedId = this.getSelectedIdFromPathname(window.location.pathname); + if (selectedId !== null && outlet.idValue === selectedId) { + outlet.markAsSelected(); + } + } + private updateSelection = (event:TurboVisitEvent) => { const url = new URL(event.detail.url, window.location.origin); - const match = /\/details\/(\d+)/.exec(url.pathname); - const selectedId = match ? Number(match[1]) : null; + const selectedId = this.getSelectedIdFromPathname(url.pathname); this.backlogsStoryOutlets.forEach((story) => { if (selectedId !== null && story.idValue === selectedId) { @@ -84,6 +90,11 @@ export default class BacklogsController extends Controller { }); }; + private getSelectedIdFromPathname(pathname:string):number|null { + const match = /\/details\/(\d+)/.exec(pathname); + return match ? Number(match[1]) : null; + } + private refreshList() { this.listElement.src = this.listUrlValue; } diff --git a/frontend/src/stimulus/controllers/dynamic/backlogs/story.controller.ts b/frontend/src/stimulus/controllers/dynamic/backlogs/story.controller.ts index e9eb3aad673..4a6f76bfcb3 100644 --- a/frontend/src/stimulus/controllers/dynamic/backlogs/story.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/backlogs/story.controller.ts @@ -65,12 +65,12 @@ export default class StoryController extends Controller implements } } - markAsSelected(_event:Event) { + markAsSelected(_event?:Event) { this.element.classList.add(this.selectedClass); this.element.setAttribute('aria-current', 'true'); } - unmarkAsSelected(_event:Event) { + unmarkAsSelected(_event?:Event) { this.element.classList.remove(this.selectedClass); this.element.removeAttribute('aria-current'); } From aaaa48f85a8ff7b2ca477b0449e7d8e6b7d0d4cd Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Wed, 4 Feb 2026 20:56:35 -0300 Subject: [PATCH 091/293] Hide unavailable story menu move items Instead of disabling "Move to top" / "Move up" for the first story and "Move down" / "Move to bottom" for the last story, hide them entirely. Co-Authored-By: Claude Opus 4.5 --- .../backlogs/story_menu_component.rb | 41 +++--------- .../backlogs/story_menu_component_spec.rb | 64 +++++++------------ 2 files changed, 32 insertions(+), 73 deletions(-) diff --git a/modules/backlogs/app/components/backlogs/story_menu_component.rb b/modules/backlogs/app/components/backlogs/story_menu_component.rb index c65d2ac9143..cfd474da13e 100644 --- a/modules/backlogs/app/components/backlogs/story_menu_component.rb +++ b/modules/backlogs/app/components/backlogs/story_menu_component.rb @@ -45,43 +45,22 @@ module Backlogs private def build_move_menu(menu) - build_move_item( - menu, - label: I18n.t(:label_sort_highest), - direction: "highest", - icon: :"move-to-top", - disabled: first_item? - ) - build_move_item( - menu, - label: I18n.t(:label_sort_higher), - direction: "higher", - icon: :"chevron-up", - disabled: first_item? - ) - build_move_item( - menu, - label: I18n.t(:label_sort_lower), - direction: "lower", - icon: :"chevron-down", - disabled: last_item? - ) - build_move_item( - menu, - label: I18n.t(:label_sort_lowest), - direction: "lowest", - icon: :"move-to-bottom", - disabled: last_item? - ) + unless first_item? + build_move_item(menu, label: I18n.t(:label_sort_highest), direction: "highest", icon: :"move-to-top") + build_move_item(menu, label: I18n.t(:label_sort_higher), direction: "higher", icon: :"chevron-up") + end + unless last_item? + build_move_item(menu, label: I18n.t(:label_sort_lower), direction: "lower", icon: :"chevron-down") + build_move_item(menu, label: I18n.t(:label_sort_lowest), direction: "lowest", icon: :"move-to-bottom") + end end - def build_move_item(menu, label:, direction:, icon:, **) + def build_move_item(menu, label:, direction:, icon:) menu.with_item( label:, tag: :button, href: reorder_backlogs_project_sprint_story_path(project, sprint, story), - form_arguments: { method: :post, inputs: [{ name: "direction", value: direction }] }, - ** + form_arguments: { method: :post, inputs: [{ name: "direction", value: direction }] } ) do |item| item.with_leading_visual_icon(icon:) end diff --git a/modules/backlogs/spec/components/backlogs/story_menu_component_spec.rb b/modules/backlogs/spec/components/backlogs/story_menu_component_spec.rb index 21daaba4a06..ecd72ae492e 100644 --- a/modules/backlogs/spec/components/backlogs/story_menu_component_spec.rb +++ b/modules/backlogs/spec/components/backlogs/story_menu_component_spec.rb @@ -112,76 +112,56 @@ RSpec.describe Backlogs::StoryMenuComponent, type: :component do describe "position logic" do context "when item is first (position=1)" do - it "disables Move to top and Move up" do + it "hides Move to top and Move up" do render_component(position: 1, max_position: 3) - # Move to top should be disabled - move_to_top = page.find("li", text: I18n.t(:label_sort_highest)) - expect(move_to_top[:class]).to include("ActionListItem--disabled") - - # Move up should be disabled - move_up = page.find("li", text: I18n.t(:label_sort_higher)) - expect(move_up[:class]).to include("ActionListItem--disabled") + expect(page).to have_no_text(I18n.t(:label_sort_highest)) + expect(page).to have_no_text(I18n.t(:label_sort_higher)) end - it "enables Move down and Move to bottom" do + it "shows Move down and Move to bottom" do render_component(position: 1, max_position: 3) - # Move down should be enabled - move_down = page.find("li", text: I18n.t(:label_sort_lower)) - expect(move_down[:class]).not_to include("ActionListItem--disabled") - - # Move to bottom should be enabled - move_to_bottom = page.find("li", text: I18n.t(:label_sort_lowest)) - expect(move_to_bottom[:class]).not_to include("ActionListItem--disabled") + expect(page).to have_text(I18n.t(:label_sort_lower)) + expect(page).to have_text(I18n.t(:label_sort_lowest)) end end context "when item is last (position=max)" do - it "disables Move down and Move to bottom" do + it "hides Move down and Move to bottom" do render_component(position: 3, max_position: 3) - # Move down should be disabled - move_down = page.find("li", text: I18n.t(:label_sort_lower)) - expect(move_down[:class]).to include("ActionListItem--disabled") - - # Move to bottom should be disabled - move_to_bottom = page.find("li", text: I18n.t(:label_sort_lowest)) - expect(move_to_bottom[:class]).to include("ActionListItem--disabled") + expect(page).to have_no_text(I18n.t(:label_sort_lower)) + expect(page).to have_no_text(I18n.t(:label_sort_lowest)) end - it "enables Move to top and Move up" do + it "shows Move to top and Move up" do render_component(position: 3, max_position: 3) - # Move to top should be enabled - move_to_top = page.find("li", text: I18n.t(:label_sort_highest)) - expect(move_to_top[:class]).not_to include("ActionListItem--disabled") - - # Move up should be enabled - move_up = page.find("li", text: I18n.t(:label_sort_higher)) - expect(move_up[:class]).not_to include("ActionListItem--disabled") + expect(page).to have_text(I18n.t(:label_sort_highest)) + expect(page).to have_text(I18n.t(:label_sort_higher)) end end context "when item is in the middle" do - it "enables all move options" do + it "shows all move options" do render_component(position: 2, max_position: 3) - expect(page.find("li", text: I18n.t(:label_sort_highest))[:class]).not_to include("ActionListItem--disabled") - expect(page.find("li", text: I18n.t(:label_sort_higher))[:class]).not_to include("ActionListItem--disabled") - expect(page.find("li", text: I18n.t(:label_sort_lower))[:class]).not_to include("ActionListItem--disabled") - expect(page.find("li", text: I18n.t(:label_sort_lowest))[:class]).not_to include("ActionListItem--disabled") + expect(page).to have_text(I18n.t(:label_sort_highest)) + expect(page).to have_text(I18n.t(:label_sort_higher)) + expect(page).to have_text(I18n.t(:label_sort_lower)) + expect(page).to have_text(I18n.t(:label_sort_lowest)) end end context "when there is only one item (position=1, max=1)" do - it "disables all move options" do + it "hides all move options" do render_component(position: 1, max_position: 1) - expect(page.find("li", text: I18n.t(:label_sort_highest))[:class]).to include("ActionListItem--disabled") - expect(page.find("li", text: I18n.t(:label_sort_higher))[:class]).to include("ActionListItem--disabled") - expect(page.find("li", text: I18n.t(:label_sort_lower))[:class]).to include("ActionListItem--disabled") - expect(page.find("li", text: I18n.t(:label_sort_lowest))[:class]).to include("ActionListItem--disabled") + expect(page).to have_no_text(I18n.t(:label_sort_highest)) + expect(page).to have_no_text(I18n.t(:label_sort_higher)) + expect(page).to have_no_text(I18n.t(:label_sort_lower)) + expect(page).to have_no_text(I18n.t(:label_sort_lowest)) end end end From 9facfb0d59dfee82e4e1e4550fd36944a658b8c7 Mon Sep 17 00:00:00 2001 From: OpenProject Actions CI Date: Thu, 5 Feb 2026 03:50:47 +0000 Subject: [PATCH 092/293] update locales from crowdin [ci skip] --- config/locales/crowdin/fr.seeders.yml | 16 +- config/locales/crowdin/fr.yml | 96 +++++----- config/locales/crowdin/js-fr.yml | 2 +- config/locales/crowdin/js-pt-PT.yml | 6 +- config/locales/crowdin/js-uk.yml | 6 +- config/locales/crowdin/pt-PT.yml | 170 ++++++++--------- config/locales/crowdin/uk.yml | 174 +++++++++--------- config/locales/crowdin/zh-CN.yml | 36 ++-- config/locales/crowdin/zh-TW.yml | 20 +- .../documents/config/locales/crowdin/fr.yml | 2 +- .../documents/config/locales/crowdin/ko.yml | 2 +- .../config/locales/crowdin/pt-BR.yml | 2 +- .../config/locales/crowdin/pt-PT.yml | 2 +- .../documents/config/locales/crowdin/uk.yml | 2 +- modules/meeting/config/locales/crowdin/fr.yml | 4 +- modules/meeting/config/locales/crowdin/ko.yml | 22 +-- .../meeting/config/locales/crowdin/pt-BR.yml | 18 +- .../meeting/config/locales/crowdin/pt-PT.yml | 34 ++-- modules/meeting/config/locales/crowdin/uk.yml | 34 ++-- .../meeting/config/locales/crowdin/zh-TW.yml | 6 +- .../storages/config/locales/crowdin/pt-PT.yml | 22 +-- .../storages/config/locales/crowdin/uk.yml | 22 +-- 22 files changed, 349 insertions(+), 349 deletions(-) diff --git a/config/locales/crowdin/fr.seeders.yml b/config/locales/crowdin/fr.seeders.yml index cbea4fbf289..8648ec98b8c 100644 --- a/config/locales/crowdin/fr.seeders.yml +++ b/config/locales/crowdin/fr.seeders.yml @@ -492,14 +492,14 @@ fr: Fonctionnalités principales et cas d'utilisation : - * [Gestion de portefeuille de projets] (https://www.openproject.org/collaboration-software-features/project-portfolio-management/) - * [Planification et organisation de projets](https://www.openproject.org/collaboration-software-features/project-planning-scheduling/) - * [Gestion des tâches et suivi des problèmes](https://www.openproject.org/collaboration-software-features/task-management/) - * [Tableaux agiles (Scrum et Kanban)](https://www.openproject.org/collaboration-software-features/agile-project-management/) - * [Gestion des exigences et planification des versions](https://www.openproject.org/collaboration-software-features/product-development/) - * [Suivi du temps et des coûts, budgets](https://www.openproject.org/collaboration-software-features/time-tracking/) - * [Collaboration d'équipe et documentation](https://www.openproject.org/collaboration-software-features/team-collaboration/) + * [Gestion de portefeuille de projets](https://www.openproject.org/fr/fonctionnalites-collaboration-logiciel/gestion-portefeuille-projets/) + * [Planification et organisation de projets](https://www.openproject.org/fr/fonctionnalites-collaboration-logiciel/planification-ordonnancement-projet/) + * [Gestion des tâches et suivi des problèmes](https://www.openproject.org/fr/fonctionnalites-collaboration-logiciel/gestion-des-taches/) + * [Tableaux agiles (Scrum et Kanban)](https://www.openproject.org/fr/fonctionnalites-collaboration-logiciel/gestion-projet-agile/) + * [Gestion des exigences et planification des versions](https://www.openproject.org/fr/fonctionnalites-collaboration-logiciel/developpement-de-produits/) + * [Suivi du temps et des coûts, budgets](https://www.openproject.org/fr/fonctionnalites-collaboration-logiciel/suivi-du-temps/) + * [Collaboration d'équipe et documentation](https://www.openproject.org/fr/fonctionnalites-collaboration-logiciel/collaboration-equipe/) Bienvenue dans l'avenir de la gestion de projet. - Pour les administrateurs : Vous pouvez modifier ce texte de bienvenue [ici] ({{opSetting:base_url}}/admin/settings/general). + Pour les administrateurs : vous pouvez modifier ce texte de bienvenue [ici]({{opSetting:base_url}}/admin/settings/general). diff --git a/config/locales/crowdin/fr.yml b/config/locales/crowdin/fr.yml index 59c0c69e9d9..bce2ab76a1e 100644 --- a/config/locales/crowdin/fr.yml +++ b/config/locales/crowdin/fr.yml @@ -1513,7 +1513,7 @@ fr: even: "doit être pair." exclusion: "est réservé." feature_disabled: n'est pas disponible. - feature_disabled_for_project: is disabled for this project. + feature_disabled_for_project: est désactivé pour ce projet. file_too_large: "est trop volumineux (la taille maximale est de %{count} octets)." filter_does_not_exist: "le filtre n'existe pas." format: "ne correspond pas au format attendu « %{expected} »." @@ -2452,7 +2452,7 @@ fr: baseline_comparison: Comparaisons de référence board_view: Tableaux avancés calculated_values: Valeurs calculées - capture_external_links: Capture External Links + capture_external_links: Capturer les liens externes internal_comments: Commentaires internes custom_actions: Actions personnalisées custom_field_hierarchies: Hiérarchies @@ -2509,7 +2509,7 @@ fr: customize_life_cycle: description: "Créez et organisez des phases de projet différentes de celles fournies par la planification du cycle de projet PM2." capture_external_links: - description: "Prevent social engineering attacks by capturing and warning about external links before users visit them." + description: "Prévenez les attaques d'ingénierie sociale en capturant et en avertissant les utilisateurs des liens externes avant qu'ils ne les consultent." work_package_query_relation_columns: description: "Vous avez besoin de voir les relations ou les éléments enfants dans la liste des lots de travaux ?" edit_attribute_groups: @@ -2853,15 +2853,15 @@ fr: #We need to include the version to invalidate outdated translations in other locales "17_1": new_features_title: > - The release contains various new features and improvements, such as: + Cette version contient plusieurs nouvelles fonctionnalités et améliorations, telles que: new_features_list: - line_0: Automated project initiation (Enterprise add-on). - line_1: "Meetings: add new or existing work packages as outcomes." - line_2: "Meetings: show participant responses in iCal subscriptions." - line_3: "Recurring meetings: duplicate agenda items to the next occurrence." - line_4: "Release to Community: Attribute highlighting." - line_5: Warning before opening external links in user-provided content (Enterprise add-on). - line_6: Improved performance and user experience, including the Activity tab and Documents module. + line_0: Lancement automatisé de projets (module complémentaire Enterprise). + line_1: "Réunions : ajoutez des work packages nouveaux ou existants en tant que résultats." + line_2: "Réunions : afficher les réponses des participants dans les abonnements iCal." + line_3: "Réunions récurrentes : dupliquez les points de l'ordre du jour lors de la prochaine réunion." + line_4: "Mise à disposition de la Communauté : Mise en évidence des attributs." + line_5: Avertissement avant l'ouverture de liens externes dans le contenu fourni par l'utilisateur (module complémentaire Enterprise). + line_6: Amélioration des performances et de l'expérience utilisateur, y compris l'onglet Activité et le module Documents. links: upgrade_enterprise_edition: "Passer à la version Enterprise" postgres_migration: "Migration de votre installation vers PostgreSQL" @@ -2939,7 +2939,7 @@ fr: label: "Ajouter…" my_account: access_tokens: - description: "Provider tokens are issued by OpenProject, allowing other applications to access it. Client tokens are issued by other applications, allowing OpenProject to access them." + description: "Les jetons de fournisseur sont émis par OpenProject, ce qui permet à d'autres applications d'y accéder. Les jetons clients sont émis par d'autres applications, permettant à OpenProject d'y accéder." no_results: title: "Aucun jeton d'accès à afficher" description: "Tous ont été désactivés. Ils peuvent être ré-activés dans le menu d'administration." @@ -2972,32 +2972,32 @@ fr: disabled_text: "Les abonnements iCalendar ne sont pas activés par l'administrateur. Veuillez contacter votre administrateur pour utiliser cette fonctionnalité." oauth_application: active_tokens: "Jetons actifs" - blank_description: "There is no third-party application access configured and active for you." - blank_title: "No OAuth application token" + blank_description: "Aucun accès à une application tierce n'est configuré et actif pour vous." + blank_title: "Pas de jeton d'application OAuth" last_used_at: "Dernière utilisation à" title: "OAuth" - table_title: "OAuth application tokens" - text_hint: "OAuth application tokens allow third-party applications to connect with this OpenProject instance." + table_title: "Jetons d'application OAuth" + text_hint: "Les jetons d'application OAuth permettent aux applications tierces de se connecter à cette instance d'OpenProject." oauth_client: - blank_description: "There are no OAuth client tokens yet." - blank_title: "No OAuth client tokens" - failed: "An error occurred and the token couldn't be removed. Please try again later." - integration_type: "Integration type" - table_title: "OAuth client tokens" - text_hint: "OAuth client tokens allow this OpenProject instance to connect with external applications, such as file storages." + blank_description: "Il n'y a pas encore de jetons clients OAuth." + blank_title: "Pas de jeton client OAuth" + failed: "Une erreur s'est produite et le jeton n'a pas pu être supprimé. Veuillez réessayer plus tard." + integration_type: "Type d'intégration" + table_title: "Jetons clients OAuth" + text_hint: "Les jetons clients OAuth permettent à cette instance d'OpenProject de se connecter à des applications externes, telles que des stockages de fichiers." title: "OAuth" - remove_token: "Do you really want to remove this token? You will need to login again on %{integration}." - removed: "OAuth client token successfully removed" - unknown_integration: "Unknown" + remove_token: "Voulez-vous vraiment supprimer ce jeton ? Vous devrez vous connecter à nouveau sur %{integration}." + removed: "Le jeton client OAuth a été supprimé avec succès" + unknown_integration: "Inconnu" token/rss: add_button: "RSS token" - blank_description: "There is no RSS token yet. You can create one using the button below." - blank_title: "No RSS token" - title: "RSS" - table_title: "RSS tokens" - text_hint: "RSS tokens allow users to keep up with the latest changes in this OpenProject instance via an external RSS reader." - static_token_name: "RSS token" - disabled_text: "RSS tokens are not enabled by the administrator. Please contact your administrator to use this feature." + blank_description: "Il n'y a pas encore de jeton RSS. Vous pouvez en créer un en utilisant le bouton ci-dessous." + blank_title: "Pas de jeton RSS" + title: "Flux RSS" + table_title: "Jeton RSS" + text_hint: "Les jetons RSS permettent aux utilisateurs de se tenir au courant des derniers changements dans cette instance d'OpenProject via un lecteur RSS externe." + static_token_name: "Jeton RSS" + disabled_text: "Les jetons RSS ne sont pas activés par l'administrateur. Veuillez contacter votre administrateur pour utiliser cette fonctionnalité." storages: unknown_storage: "Espace de stockage inconnu" notifications: @@ -3315,7 +3315,7 @@ fr: label_journal_diff: "Comparaison de description" label_language: "Langue" label_languages: "Langues" - label_external_links: "External links" + label_external_links: "Liens externes" label_locale: "Langue et région" label_jump_to_a_project: "Aller à un projet…" label_keyword_plural: "Mots clés" @@ -4309,9 +4309,9 @@ fr: setting_allowed_link_protocols: "Protocoles de lien autorisés" setting_allowed_link_protocols_text_html: >- Permettre que ces protocoles soient affichés sous forme de liens dans les descriptions des lots de travaux, les champs de texte long et les commentaires. Par exemple, %{tel_code} ou %{element_code}. Saisissez un protocole par ligne.
    Les protocoles %{http_code}, %{https_code} et %{mailto_code} sont toujours autorisés. - setting_capture_external_links: "Capture external links" + setting_capture_external_links: "Saisir les liens externes" setting_capture_external_links_text: > - When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + Lorsque cette option est activée, tous les liens externes en texte formaté sont redirigés vers une page d'avertissement avant de quitter l'application. Cela permet de protéger les utilisateurs contre les sites web externes potentiellement malveillants. setting_after_first_login_redirect_url: "Redirection de première connexion" setting_after_first_login_redirect_url_text_html: > Définissez un chemin pour rediriger les utilisateurs après leur première connexion. S’il est vide, les utilisateurs seront redirigés vers la page d'accueil de la visite d'intégration.
    Exemple : /my/page @@ -4325,16 +4325,16 @@ fr: Si CORS est activé, ce sont les origines qui sont autorisées à accéder à l'API OpenProject.
    Veuillez vérifier la documentation sur l'en-tête Origin sur la façon de spécifier les valeurs attendues. setting_apiv3_write_readonly_attributes: "Accès en écriture aux attributs en lecture seule" setting_apiv3_write_readonly_attributes_instructions: > - If enabled, the API will allow administrators to write static read-only attributes during creation, such as createdAt and author. + Si cette option est activée, l'API permettra aux administrateurs d'écrire des attributs statiques en lecture seule lors de la création, tels que createdAt et author. setting_apiv3_write_readonly_attributes_warning: > - This setting has a use-case for e.g., importing data, but allows administrators to impersonate the creation of items as other users. All creation requests are being logged however with the true author. + Ce paramètre est utile, par exemple, pour l'importation de données, mais il permet aux administrateurs de se faire passer pour d'autres utilisateurs lors de la création d'éléments. Toutes les demandes de création sont toutefois enregistrées avec le véritable auteur. setting_apiv3_write_readonly_attributes_additional: > - For more information on attributes and supported resources, please see the %{api_documentation_link}. + Pour plus d'informations sur les attributs et les ressources prises en charge, veuillez consulter le site %{api_documentation_link}. setting_apiv3_max_page_size: "Taille maximale de page d'API" setting_apiv3_max_page_size_instructions: > - Set the maximum page size the API will respond with. It will not be possible to perform API requests that return more values on a single page. + Définissez la taille maximale de la page à laquelle l'API répondra. Il ne sera pas possible d'effectuer des demandes d'API qui renvoient plus de valeurs sur une seule page. setting_apiv3_max_page_size_warning: > - Please only change this value if you are sure why you need it. Setting to a high value will result in significant performance impacts, while a value lower than the per page options will cause errors in paginated views. + Ne modifiez cette valeur que si vous êtes certain d'en avoir besoin. Une valeur élevée aura un impact significatif sur les performances, tandis qu'une valeur inférieure aux options par page provoquera des erreurs dans les vues paginées. setting_apiv3_docs: "Documentation" setting_apiv3_docs_enabled: "Activer la page docs" setting_apiv3_docs_enabled_instructions_html: > @@ -4519,7 +4519,7 @@ fr: omniauth_direct_login_hint_html: > Si cette option est activée, les demandes de connexion seront redirigées vers le fournisseur omniauth configuré. La liste déroulante de connexion et la page de connexion seront désactivées.
    Remarque : lorsque cette option est activée, les utilisateurs peuvent toujours se connecter en interne en visitant la page de connexion %{internal_path}, sauf si vous désactivez également les connexions par mot de passe. remapping_existing_users_hint: > - If enabled, allows any configured identity provider to login existing users based on their username, even if the user never signed in through that provider before. This can be useful when migrating the OpenProject instance to a new SSO provider, but is not recommended when using a provider that is not trusted by all users of your instance. + Si activé, permet à tout fournisseur d'identité configuré de connecter les utilisateurs existants en se basant sur leur nom d'utilisateur, même si l'utilisateur ne s'est jamais connecté via ce fournisseur auparavant. Ceci peut être utile lors de la migration de l'instance OpenProject vers un nouveau fournisseur SSO, mais n'est pas recommandé lors de l'utilisation d'un fournisseur qui n'est pas reconnu par tous les utilisateurs de votre instance. attachments: whitelist_text_html: > Définissez une liste d'extensions de fichiers et/ou de types MIME valides pour les fichiers téléversés.
    Entrez les extensions de fichier (par exemple, %{ext_example}) ou les types mime (par exemple, %{mime_example}).
    Laissez vide pour permettre le téléversement de tout type de fichier. Plusieurs valeurs autorisées (une ligne pour chaque valeur). @@ -4625,7 +4625,7 @@ fr: project_mandate: "Mandat du projet" submission: description_template: > - **This work package was automatically created upon completion of the %{wizard_name} workflow.** A PDF artifact containing all submitted information has been generated and attached to this work package for reference and audit purposes. If you need to update or re-run the initiation steps, you can reopen the wizard at any time by using the link below: + **Ce dossier de travail a été créé automatiquement à l'issue du flux de travail %{wizard_name} .** Un artefact PDF contenant toutes les informations soumises a été généré et joint à ce dossier de travail à des fins de référence et d'audit. Si vous avez besoin de mettre à jour ou de réexécuter les étapes d'initiation, vous pouvez rouvrir l'assistant à tout moment en utilisant le lien ci-dessous : description: "Lorsqu'un utilisateur envoie une demande de lancement de projet, un nouveau lot de travail est créé avec l'artefact de la demande en pièce jointe au format PDF. Les paramètres ci-dessous définissent le type, le statut et le destinataire de ce nouveau lot de travaux." work_package_type: "Type de lot de travaux" work_package_type_caption: "Le type de lot de travaux qui doit être utilisé pour stocker l'artefact terminé." @@ -4884,8 +4884,8 @@ fr: other: "verrouillé temporairement (%{count} tentatives de connexion échouées)" confirm_status_change: "Vous êtes sur le point de changer le statut de '%{name}'. Voulez-vous vraiment continuer ?" deleted: "Utilisateur supprimé" - error_status_change_self: "You cannot change your own user status." - error_admin_change_on_non_admin: "Only administrators can change the status of administrator users." + error_status_change_self: "Vous ne pouvez pas modifier votre propre statut d'utilisateur." + error_admin_change_on_non_admin: "Seuls les administrateurs peuvent modifier le statut des utilisateurs administrateurs." error_status_change_failed: "Le changement du statut de l'utilisateur a échoué dû aux erreurs suivantes: %{errors}" invite: Inviter l'utilisateur par e-mail invited: invité @@ -5290,7 +5290,7 @@ fr: hashed_token: display_value_placeholder: "***" external_link_warning: - title: "Leaving OpenProject" - warning_message: "You are about to leave OpenProject and visit an external website. Please be aware that external websites are not under our control and may have different privacy and security policies." - continue_message: "Are you sure you want to proceed to the following external link?" - continue_button: "Continue to external website" + title: "Quitter OpenProject" + warning_message: "Vous êtes sur le point de quitter OpenProject et de visiter un site web externe. Veuillez noter que les sites web externes ne sont pas sous notre contrôle et peuvent avoir des politiques de confidentialité et de sécurité différentes." + continue_message: "Êtes-vous sûr de vouloir passer au lien externe suivant ?" + continue_button: "Continuer vers le site web externe" diff --git a/config/locales/crowdin/js-fr.yml b/config/locales/crowdin/js-fr.yml index 94cc3cd117b..27c30489db1 100644 --- a/config/locales/crowdin/js-fr.yml +++ b/config/locales/crowdin/js-fr.yml @@ -426,7 +426,7 @@ fr: label_remove_row: "Supprimer la ligne" label_report: "Rapport" label_repository_plural: "Dépôts" - label_resize_project_menu: "Resize project menu" + label_resize_project_menu: "Redimensionner le menu du projet" label_save_as: "Enregistrer sous" label_search_columns: "Rechercher une colonne" label_select_watcher: "Sélectionner un observateur…" diff --git a/config/locales/crowdin/js-pt-PT.yml b/config/locales/crowdin/js-pt-PT.yml index 4daa93368c1..9e400a1d7be 100644 --- a/config/locales/crowdin/js-pt-PT.yml +++ b/config/locales/crowdin/js-pt-PT.yml @@ -203,8 +203,8 @@ pt-PT: add_table: "Adicionar tabela de pacotes de trabalho relacionados" edit_query: "Editar consulta" new_group: "Novo Grupo" - delete_group: "Delete group" - remove_attribute: "Remove from group" + delete_group: "Eliminar grupo" + remove_attribute: "Remover do grupo" reset_to_defaults: "Repor predefinições" working_days: calendar: @@ -426,7 +426,7 @@ pt-PT: label_remove_row: "Remover linha" label_report: "Relatório" label_repository_plural: "Repositórios" - label_resize_project_menu: "Resize project menu" + label_resize_project_menu: "Redimensionar menu do projeto" label_save_as: "Guardar como" label_search_columns: "Pesquisar uma coluna" label_select_watcher: "Selecione um \"observador\"..." diff --git a/config/locales/crowdin/js-uk.yml b/config/locales/crowdin/js-uk.yml index 46dd033c41f..5b026641325 100644 --- a/config/locales/crowdin/js-uk.yml +++ b/config/locales/crowdin/js-uk.yml @@ -203,8 +203,8 @@ uk: add_table: "Додати таблицю пов'язаних пакетів робіт" edit_query: "Редагувати запит" new_group: "Нова група" - delete_group: "Delete group" - remove_attribute: "Remove from group" + delete_group: "Видалити групу" + remove_attribute: "Вилучити з групи" reset_to_defaults: "Скинути на типові значення" working_days: calendar: @@ -426,7 +426,7 @@ uk: label_remove_row: "Видалити рядок" label_report: "Звіт" label_repository_plural: "Репозиторії" - label_resize_project_menu: "Resize project menu" + label_resize_project_menu: "Змінити розмір меню проекту" label_save_as: "Зберегти як" label_search_columns: "Пошук стовпця" label_select_watcher: "Виберіть спостерігача..." diff --git a/config/locales/crowdin/pt-PT.yml b/config/locales/crowdin/pt-PT.yml index 64332c97b7e..eaf09b9d2bc 100644 --- a/config/locales/crowdin/pt-PT.yml +++ b/config/locales/crowdin/pt-PT.yml @@ -111,21 +111,21 @@ pt-PT: link: "webhook" mcp_configurations: index: - description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." - resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." - resources_submit: "Update resources" - tools_heading: "Tools" - tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." - tools_submit: "Update tools" + description: "O protocolo de contexto do modelo permite que os agentes de IA forneçam aos seus utilizadores ferramentas e recursos expostos por esta instância do OpenProject." + resources_heading: "Recursos" + resources_description: "O OpenProject implementa as seguintes ferramentas. Cada uma pode ser ativada, renomeada e descrita como quiser. Para mais informações, consulte a [documentação sobre recursos MCP](docs_url)." + resources_submit: "Atualizar recursos" + tools_heading: "Ferramentas" + tools_description: "O OpenProject implementa as seguintes ferramentas. Cada uma pode ser ativada, renomeada e descrita como quiser. Para mais informações, consulte a [documentação sobre ferramentas MCP](docs_url)." + tools_submit: "Atualizar ferramentas" multi_update: - success: "MCP configurations were updated successfully." + success: "As configurações da MCP foram atualizadas com êxito." server_form: - description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + description_caption: "Como o servidor MCP será descrito a outras aplicações que se ligam a ele." + title_caption: "Um título curto apresentado às aplicações que se ligam ao servidor MCP." update: - failure: "MCP configuration could not be updated." - success: "MCP configuration was updated successfully." + failure: "Não foi possível atualizar a configuração MCP." + success: "A configuração da MCP foi atualizada com êxito." scim_clients: authentication_methods: sso: "JWT do fornecedor de identidade" @@ -728,11 +728,11 @@ pt-PT: create_button: "Criar" name_label: "Nome do token" created_dialog: - one_time_warning: "This is the only time you will see this token. Make sure to copy it now." + one_time_warning: "Esta é a única vez que verá este token. Não se esqueça de o copiar agora." token/api: title: "O token da API foi gerado" token/rss: - title: "The RSS token has been generated" + title: "O token RSS foi gerado" failed_to_reset_token: "Não foi possível reiniciar o token de acesso: %{error}" failed_to_create_token: "Não foi possível criar o token de acesso: %{error}" failed_to_revoke_token: "Não foi possível revogar o token de acesso: %{error}" @@ -1251,9 +1251,9 @@ pt-PT: port: "Porta" tls_certificate_string: "Certificado SSL do servidor LDAP" mcp_configuration: - enabled: Enabled - title: Title - description: Description + enabled: Ativado + title: Título + description: Descrição member: roles: "Papel" notification: @@ -1512,7 +1512,7 @@ pt-PT: even: "deve ser par." exclusion: "é reservado." feature_disabled: não está disponível. - feature_disabled_for_project: is disabled for this project. + feature_disabled_for_project: está desativado neste projeto. file_too_large: "é muito grande (tamanho máximo é %{count} Bytes)." filter_does_not_exist: "filtro não existe." format: "não corresponde ao formato esperado '%{expected}'." @@ -1945,8 +1945,8 @@ pt-PT: one: Token de acesso other: Tokens de acesso token/rss: - one: "RSS token" - other: "RSS tokens" + one: "Token RSS" + other: "Tokens RSS" type: one: "Tipo" other: "Tipos" @@ -2451,7 +2451,7 @@ pt-PT: baseline_comparison: Comparações de base de referência board_view: Quadros avançados calculated_values: Valores calculados - capture_external_links: Capture External Links + capture_external_links: Capturar links externos internal_comments: Comentários internos custom_actions: Ações personalizadas custom_field_hierarchies: Hierarquias @@ -2461,12 +2461,12 @@ pt-PT: edit_attribute_groups: Editar grupos de atributos gantt_pdf_export: Exportação de PDF de Gantt ldap_groups: Sincronização de utilizadores e grupos LDAP - mcp_server: MCP Server + mcp_server: Servidor MCP nextcloud_sso: Início de sessão único para armazenamento Nextcloud one_drive_sharepoint_file_storage: Armazenamento de ficheiros no OneDrive/SharePoint placeholder_users: Utilizadores de marcador de posição portfolio_management: Gestão de carteiras - project_creation_wizard: Project initiation request + project_creation_wizard: Pedido de início do projeto project_list_sharing: Partilha da lista de projetos readonly_work_packages: Pacotes de trabalho só de leitura scim_api: API do servidor SCIM @@ -2508,7 +2508,7 @@ pt-PT: customize_life_cycle: description: "Crie e organize fases de projeto diferentes das fornecidas pelo planeamento do ciclo de projeto PM2." capture_external_links: - description: "Prevent social engineering attacks by capturing and warning about external links before users visit them." + description: "Evite ataques de engenharia social ao identificar e alertar para links externos antes de os utilizadores os visitarem." work_package_query_relation_columns: description: "Precisa de ver as relações ou elementos secundários na lista do pacote de trabalho?" edit_attribute_groups: @@ -2539,7 +2539,7 @@ pt-PT: title: "Ações personalizadas" description: "As ações personalizadas são atalhos de um clique para um conjunto de ações predefinidas que pode disponibilizar em determinados pacotes de trabalho com base no estado, função, tipo ou projeto." mcp_server: - description: "Integrate AI agents with your OpenProject instance through MCP." + description: "Integre agentes de IA com a sua instância do OpenProject através de MCP." nextcloud_sso: title: "Início de sessão único para armazenamento Nextcloud" description: "Permita uma autenticação perfeita e segura para o seu armazenamento Nextcloud com o início de sessão único. Simplifique a gestão do acesso e aumente a conveniência do utilizador." @@ -2552,7 +2552,7 @@ pt-PT: virus_scanning: description: "Assegure-se que os ficheiros carregados no OpenProject são verificados quanto à presença de vírus antes de serem acessíveis a outros utilizadores." project_creation_wizard: - description: "Generate a step-by-step wizard to help project managers fill out a project initiation request." + description: "Crie um assistente passo a passo para ajudar os gestores de projetos a preencher um pedido de iniciação de projeto." placeholder_users: title: Utilizadores de espaço reservado description: > @@ -2852,15 +2852,15 @@ pt-PT: #We need to include the version to invalidate outdated translations in other locales "17_1": new_features_title: > - The release contains various new features and improvements, such as: + A versão contém várias novas funcionalidades e melhorias, tais como: new_features_list: - line_0: Automated project initiation (Enterprise add-on). - line_1: "Meetings: add new or existing work packages as outcomes." - line_2: "Meetings: show participant responses in iCal subscriptions." - line_3: "Recurring meetings: duplicate agenda items to the next occurrence." - line_4: "Release to Community: Attribute highlighting." - line_5: Warning before opening external links in user-provided content (Enterprise add-on). - line_6: Improved performance and user experience, including the Activity tab and Documents module. + line_0: Iniciação automatizada de projetos (complemento Enterprise). + line_1: "Reuniões: adicione pacotes de trabalho novos ou existentes como resultados." + line_2: "Reuniões: mostrar as respostas dos participantes nas subscrições iCal." + line_3: "Reuniões recorrentes: duplicar os pontos da agenda para a próxima ocorrência." + line_4: "Disponibilizar para a Comunidade: Destaque dos atributos." + line_5: Aviso antes de abrir ligações externas em conteúdos fornecidos pelo utilizador (complemento Enterprise). + line_6: Melhoria do desempenho e da experiência do utilizador, incluindo o separador Atividade e o módulo Documentos. links: upgrade_enterprise_edition: "Aprimorar para a edição Enterprise" postgres_migration: "A migrar a sua instalação para PostgreSQL" @@ -2928,17 +2928,17 @@ pt-PT: instructions_after_error: "Pode tentar entrar novamente clicando em %{signin}. Se o erro persistir, peça ajuda ao seu administrador." menus: admin: - ai: "Artificial Intelligence (AI)" + ai: "Inteligência artificial (IA)" aggregation: "Agregação" api_and_webhooks: "API e webhooks" mail_notification: "Notificações por e-mail" mails_and_notifications: "E-mails e notificações" - mcp_configurations: "Model Context Protocol (MCP)" + mcp_configurations: "Protocolo de contexto de modelo (MCP)" quick_add: label: "Adicionar…" my_account: access_tokens: - description: "Provider tokens are issued by OpenProject, allowing other applications to access it. Client tokens are issued by other applications, allowing OpenProject to access them." + description: "Os tokens de fornecedor são emitidos pelo OpenProject e permitem o acesso de outras aplicações. Os tokens de cliente são emitidos por outras aplicações e permitem que o OpenProject lhes aceda." no_results: title: "Sem tokens de acesso para exibir" description: "Todos foram desativados. Podem ser reativados no menu Administração." @@ -2950,53 +2950,53 @@ pt-PT: simple_revoke_confirmation: "Tem a certeza de que deseja revogar este token?" tabs: client: - title: "Client tokens" + title: "Tokens de cliente" provider: - title: "Provider tokens" + title: "Tokens de fornecedor" token/api: - blank_description: "There is no API token yet. You can create one using the button below." - blank_title: "No API token" + blank_description: "Ainda não existe um token de API. Pode criar um no botão abaixo." + blank_title: "Nenhum token de API" title: "API" - table_title: "API tokens" + table_title: "Tokens de API" text_hint: "Os tokens de API permitem que aplicações de terceiros comuniquem com esta instância do OpenProject através de API REST." - static_token_name: "API token" + static_token_name: "Token de API" disabled_text: "Os tokens da API não são ativados pelo administrador. Contacte o seu administrador para utilizar esta funcionalidade." - add_button: "API token" + add_button: "Token de API" ical: - blank_description: "To add an iCalendar token, subscribe to a new or existing calendar from within the Calendar module of a project. You must have the necessary permissions." - blank_title: "No iCalendar token" + blank_description: "Para adicionar um token do iCalendar, subscreva um calendário novo ou já existente a partir do módulo Calendários de um projeto. Tem de ter as permissões necessárias." + blank_title: "Nenhum token iCalendar" title: "iCalendar" - table_title: "iCalendar tokens" + table_title: "Tokens iCalendar" text_hint_link: "Os tokens iCalendar permitem que os utilizadores [subscrevam os calendários do OpenProject](docs_url) e vejam informações atualizadas sobre pacotes de trabalho de clientes externos." disabled_text: "As subscrições do iCalendar não são ativadas pelo administrador. Contacte o seu administrador para utilizar esta funcionalidade." oauth_application: - active_tokens: "Active tokens" - blank_description: "There is no third-party application access configured and active for you." - blank_title: "No OAuth application token" - last_used_at: "Last used at" + active_tokens: "Tokens ativos" + blank_description: "Não existe um acesso a aplicações de terceiros configurado e ativo para si." + blank_title: "Nenhum token de aplicação OAuth" + last_used_at: "Data de última utilização" title: "OAuth" - table_title: "OAuth application tokens" - text_hint: "OAuth application tokens allow third-party applications to connect with this OpenProject instance." + table_title: "Tokens de aplicação OAuth" + text_hint: "Os tokens de aplicação OAuth permitem que aplicações de terceiros se liguem a esta instância do OpenProject." oauth_client: - blank_description: "There are no OAuth client tokens yet." - blank_title: "No OAuth client tokens" - failed: "An error occurred and the token couldn't be removed. Please try again later." - integration_type: "Integration type" - table_title: "OAuth client tokens" - text_hint: "OAuth client tokens allow this OpenProject instance to connect with external applications, such as file storages." + blank_description: "Ainda não existem tokens de cliente OAuth." + blank_title: "Nenhum token de cliente OAuth" + failed: "Ocorreu um erro e não foi possível remover o token. Tente novamente mais tarde." + integration_type: "Tipo de integração" + table_title: "Tokens do cliente OAuth" + text_hint: "Os tokens de cliente OAuth permitem que esta instância do OpenProject se ligue a aplicações externas, como armazenamentos de ficheiros." title: "OAuth" - remove_token: "Do you really want to remove this token? You will need to login again on %{integration}." - removed: "OAuth client token successfully removed" - unknown_integration: "Unknown" + remove_token: "Tem a certeza de que deseja remover este token? Terá de iniciar sessão novamente em %{integration}." + removed: "Token de cliente OAuth removido com sucesso" + unknown_integration: "Desconhecido" token/rss: - add_button: "RSS token" - blank_description: "There is no RSS token yet. You can create one using the button below." - blank_title: "No RSS token" + add_button: "Token RSS" + blank_description: "Ainda não existe um token RSS. Pode criar um no botão abaixo." + blank_title: "Nenhum token RSS" title: "RSS" - table_title: "RSS tokens" - text_hint: "RSS tokens allow users to keep up with the latest changes in this OpenProject instance via an external RSS reader." - static_token_name: "RSS token" - disabled_text: "RSS tokens are not enabled by the administrator. Please contact your administrator to use this feature." + table_title: "Tokens RSS" + text_hint: "Os tokens RSS permitem aos utilizadores manterem-se a par das últimas alterações nesta instância do OpenProject através de um leitor RSS externo." + static_token_name: "Token RSS" + disabled_text: "Os tokens RSS não são ativados pelo administrador. Contacte o seu administrador para utilizar esta funcionalidade." storages: unknown_storage: "Armazenamento desconhecido" notifications: @@ -3314,7 +3314,7 @@ pt-PT: label_journal_diff: "Comparação de descrição" label_language: "Idioma" label_languages: "Idiomas" - label_external_links: "External links" + label_external_links: "Links externos" label_locale: "Idioma e região" label_jump_to_a_project: "Saltar para um projeto..." label_keyword_plural: "Palavras-chave" @@ -4305,9 +4305,9 @@ pt-PT: setting_allowed_link_protocols: "Protocolos de ligação permitidos" setting_allowed_link_protocols_text_html: >- Permitir que estes protocolos sejam apresentados como ligações nas descrições do pacote de trabalho, campos de texto longos e comentários. Por exemplo, %{tel_code} ou %{element_code}. Introduza um protocolo por linha.
    Protocolos %{http_code}, %{https_code} e %{mailto_code} são sempre permitidos. - setting_capture_external_links: "Capture external links" + setting_capture_external_links: "Capturar links externos" setting_capture_external_links_text: > - When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + Quando ativada, todos os links externos no texto formatado serão redirecionados através de uma página de aviso antes de sair da aplicação. Isto ajuda a proteger os utilizadores de sites externos potencialmente maliciosos. setting_after_first_login_redirect_url: "Redirecionamento do primeiro início de sessão" setting_after_first_login_redirect_url_text_html: > Defina um caminho para redirecionar os utilizadores após o primeiro início de sessão. Se estiver vazio, redireciona para a página inicial do tour de integração.
    Exemplo: /my/page @@ -4321,16 +4321,16 @@ pt-PT: Se o CORS estiver habilitado, estas são as origens que têm permissão para aceder ao API OpenProject.
    Por favor, verifique a documentação no cabeçalho da Origem sobre como especificar os valores esperados. setting_apiv3_write_readonly_attributes: "Acesso de escrita a atributos só de leitura" setting_apiv3_write_readonly_attributes_instructions: > - If enabled, the API will allow administrators to write static read-only attributes during creation, such as createdAt and author. + Se ativada, a API permitirá que os administradores escrevam atributos estáticos só de leitura durante a criação, tais como createdAt e author. setting_apiv3_write_readonly_attributes_warning: > - This setting has a use-case for e.g., importing data, but allows administrators to impersonate the creation of items as other users. All creation requests are being logged however with the true author. + Esta definição tem um caso de utilização para, por exemplo, importar dados, mas permite que os administradores se façam passar por outros utilizadores na criação de elementos. No entanto, todos os pedidos de criação são registados com o verdadeiro autor. setting_apiv3_write_readonly_attributes_additional: > - For more information on attributes and supported resources, please see the %{api_documentation_link}. + Para mais informações sobre atributos e recursos suportados, consulte %{api_documentation_link}. setting_apiv3_max_page_size: "Tamanho máximo da página de API" setting_apiv3_max_page_size_instructions: > - Set the maximum page size the API will respond with. It will not be possible to perform API requests that return more values on a single page. + Defina o tamanho máximo da página com que a API responderá. Não será possível executar pedidos de API que devolvam mais valores numa única página. setting_apiv3_max_page_size_warning: > - Please only change this value if you are sure why you need it. Setting to a high value will result in significant performance impacts, while a value lower than the per page options will cause errors in paginated views. + Só altere este valor se tiver a certeza de que é necessário. Definir um valor elevado terá um impacto significativo no desempenho, enquanto um valor inferior ao número de elementos por página provocará erros nas visualizações paginadas. setting_apiv3_docs: "Documentação" setting_apiv3_docs_enabled: "Ativar página de documentos" setting_apiv3_docs_enabled_instructions_html: > @@ -4515,7 +4515,7 @@ pt-PT: omniauth_direct_login_hint_html: > Se esta opção estiver ativa, os pedidos de início de sessão serão redirecionados para o fornecedor omniauth configurado. O menu pendente de início de sessão e a página de início de sessão serão desativados.
    Nota: a menos que também desative os inícios de sessão com palavra-passe, com esta opção ativada, os utilizadores ainda podem iniciar sessão internamente ao visitar a página de início de sessão %{internal_path}. remapping_existing_users_hint: > - If enabled, allows any configured identity provider to login existing users based on their username, even if the user never signed in through that provider before. This can be useful when migrating the OpenProject instance to a new SSO provider, but is not recommended when using a provider that is not trusted by all users of your instance. + Quando ativada, esta opção permite que qualquer fornecedor de identidade configurado inicie a sessão de utilizadores existentes com base no respetivo nome de utilizador, mesmo que estes nunca tenham iniciado sessão através desse fornecedor anteriormente. Isto pode ser útil ao migrar a instância do OpenProject para um novo fornecedor de SSO, mas não é recomendado quando se utiliza um fornecedor que não seja de confiança para todos os utilizadores da instância. attachments: whitelist_text_html: > Defina uma lista de extensões de ficheiros e/ou de tipos mime para ficheiros carregados.
    Insira extensões de ficheiro (por exemplo, %{ext_example}) ou tipos mime (ex., %{mime_example}).
    Deixe em branco para permitir que qualquer tipo de ficheiro seja carregado. Vários valores permitidos (uma linha para cada valor). @@ -4621,7 +4621,7 @@ pt-PT: project_mandate: "Mandato do projeto" submission: description_template: > - **This work package was automatically created upon completion of the %{wizard_name} workflow.** A PDF artifact containing all submitted information has been generated and attached to this work package for reference and audit purposes. If you need to update or re-run the initiation steps, you can reopen the wizard at any time by using the link below: + **Este pacote de trabalho foi criado automaticamente após a conclusão do fluxo de trabalho %{wizard_name}.** Foi gerado e anexado a este pacote de trabalho um artefacto PDF com todas as informações submetidas, para efeitos de referência e auditoria. Se precisar de atualizar ou voltar a executar os passos de iniciação, pode reabrir o assistente em qualquer altura, com a ligação abaixo: description: "Quando um utilizador envia um pedido de iniciação de projeto, é criado um novo pacote de trabalho com o artefacto do pedido anexado como ficheiro PDF. As configurações abaixo definem o tipo, o estado e o responsável por este novo pacote de trabalho." work_package_type: "Tipo de pacote de trabalho" work_package_type_caption: "O tipo de pacote de trabalho que deve ser utilizado para armazenar o artefacto concluído." @@ -4880,8 +4880,8 @@ pt-PT: other: "bloqueada temporariamente (%{count} tentativas de início de sessão falhadas)" confirm_status_change: "Está prestes a mudar o estado de '%{name}'. Tem certeza que deseja continuar?" deleted: "Eliminar Utilizador" - error_status_change_self: "You cannot change your own user status." - error_admin_change_on_non_admin: "Only administrators can change the status of administrator users." + error_status_change_self: "Não é possível alterar o seu próprio estado de utilizador." + error_admin_change_on_non_admin: "Só os administradores podem alterar o estado dos utilizadores administradores." error_status_change_failed: "A alteração do estado do utilizador falhou devido aos seguintes erros: %{errors}" invite: Convidar utilizador via email invited: convidado @@ -5287,7 +5287,7 @@ pt-PT: hashed_token: display_value_placeholder: "***" external_link_warning: - title: "Leaving OpenProject" - warning_message: "You are about to leave OpenProject and visit an external website. Please be aware that external websites are not under our control and may have different privacy and security policies." - continue_message: "Are you sure you want to proceed to the following external link?" - continue_button: "Continue to external website" + title: "Sair do OpenProject" + warning_message: "Está prestes a sair do OpenProject e a visitar um site externo. Tenha em atenção que os sites externos não estão sob o nosso controlo, e podem ter políticas de privacidade e de segurança diferentes." + continue_message: "Tem a certeza de que quer aceder à seguinte ligação externa?" + continue_button: "Continuar para o site externo" diff --git a/config/locales/crowdin/uk.yml b/config/locales/crowdin/uk.yml index 6a2de6b4685..ff0279c7307 100644 --- a/config/locales/crowdin/uk.yml +++ b/config/locales/crowdin/uk.yml @@ -111,21 +111,21 @@ uk: link: "вебгука" mcp_configurations: index: - description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." - resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." - resources_submit: "Update resources" - tools_heading: "Tools" - tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." - tools_submit: "Update tools" + description: "Протокол контексту моделі дає змогу агентам ШІ надавати своїм користувачам інструменти й ресурси, доступні в цьому екземплярі OpenProject." + resources_heading: "Ресурси" + resources_description: "OpenProject реалізує наведені нижче інструменти. Кожен із них можна ввімкнути, перейменувати й описати як завгодно. Щоб дізнатися більше, ознайомтеся з [документацією на ресурсах MCP] (docs_url)." + resources_submit: "Оновити ресурси" + tools_heading: "Інструменти" + tools_description: "OpenProject реалізує наведені нижче інструменти. Кожен із них можна ввімкнути, перейменувати й описати як завгодно. Щоб дізнатися більше, ознайомтеся з [документацією з інструментів MCP] (docs_url)." + tools_submit: "Оновити інструменти" multi_update: - success: "MCP configurations were updated successfully." + success: "Конфігурації MCP оновлено." server_form: - description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + description_caption: "Як MCP-сервер буде описано іншим додаткам, які підключаються до нього." + title_caption: "Короткий заголовок, який показується додаткам, що підключаються до сервера MCP." update: - failure: "MCP configuration could not be updated." - success: "MCP configuration was updated successfully." + failure: "Конфігурацію MCP не можна оновити." + success: "Конфігурацію MCP оновлено." scim_clients: authentication_methods: sso: "JWT від постачальника ідентифікаційних даних" @@ -741,11 +741,11 @@ uk: create_button: "Створити" name_label: "Ім’я маркера" created_dialog: - one_time_warning: "This is the only time you will see this token. Make sure to copy it now." + one_time_warning: "Ви більше не побачите цей маркер. Обов’язково скопіюйте його зараз." token/api: title: "Маркер API згенеровано" token/rss: - title: "The RSS token has been generated" + title: "Маркер RSS згенеровано" failed_to_reset_token: "Не вдалося відновити маркер доступу: %{error}" failed_to_create_token: "Не вдалося створити маркер доступу: %{error}" failed_to_revoke_token: "Не вдалося відкликати маркер доступу: %{error}" @@ -1266,9 +1266,9 @@ uk: port: "Порт" tls_certificate_string: "Сертифікат SSL сервера LDAP" mcp_configuration: - enabled: Enabled - title: Title - description: Description + enabled: Увімкнено + title: Заголовок + description: Опис member: roles: "Роль" notification: @@ -1527,7 +1527,7 @@ uk: even: "має бути рівним." exclusion: "зарезервовано." feature_disabled: недоступна. - feature_disabled_for_project: is disabled for this project. + feature_disabled_for_project: вимкнено для цього проєкту. file_too_large: "занадто великий (максимальний розмір -%{count} байт)" filter_does_not_exist: "фільтр не існує." format: "не відповідає очікуваному формату «%{expected}»." @@ -1986,10 +1986,10 @@ uk: many: Маркери доступу other: Маркери доступу token/rss: - one: "RSS token" - few: "RSS tokens" - many: "RSS tokens" - other: "RSS tokens" + one: "Маркер RSS" + few: "Маркери RSS" + many: "Маркери RSS" + other: "Маркери RSS" type: one: "Тип" few: "Типи" @@ -2544,7 +2544,7 @@ uk: baseline_comparison: Порівняння з вихідним рівнем board_view: Покращені Дошки calculated_values: Розраховані значення - capture_external_links: Capture External Links + capture_external_links: Захоплення зовнішніх посилань internal_comments: Внутрішні коментарі custom_actions: Користувацькі дії custom_field_hierarchies: Ієрархії @@ -2554,12 +2554,12 @@ uk: edit_attribute_groups: Редагування груп атрибутів gantt_pdf_export: Експорт діаграми Ґантта в PDF ldap_groups: Синхронізація користувачів і груп LDAP - mcp_server: MCP Server + mcp_server: Сервер MCP nextcloud_sso: Єдиний вхід для сховища Nextcloud one_drive_sharepoint_file_storage: Сховище файлів OneDrive / SharePoint placeholder_users: Прототипи користувачів portfolio_management: Керування портфелем - project_creation_wizard: Project initiation request + project_creation_wizard: Запит на ініціювання проєкту project_list_sharing: Спільне використання списку проєктів readonly_work_packages: Пакети робіт лише для читання scim_api: API сервера SCIM @@ -2601,7 +2601,7 @@ uk: customize_life_cycle: description: "Створюйте й упорядковуйте етапи й проєкту, відмінні від тих, що передбачено плануванням циклу проєкту PM2." capture_external_links: - description: "Prevent social engineering attacks by capturing and warning about external links before users visit them." + description: "Запобігайте атакам соціальної інженерії, перехоплюючи зовнішні посилання й попереджаючи про них користувачів до того, як вони перейдуть за ними." work_package_query_relation_columns: description: "Хочете переглядати зв’язки або дочірні елементи в списку пакетів робіт?" edit_attribute_groups: @@ -2632,7 +2632,7 @@ uk: title: "Користувацькі дії" description: "Користувацькі дії – це ярлики набору попередньо визначених дій, які ви можете включати в певні пакети робіт залежно від статусу, ролі, типу або проєкту." mcp_server: - description: "Integrate AI agents with your OpenProject instance through MCP." + description: "Інтегруйте ШІ-агентів у свій екземпляр OpenProject за допомогою MCP." nextcloud_sso: title: "Єдиний вхід для сховища Nextcloud" description: "Увімкніть швидку й безпечну автентифікацію для свого сховища Nextcloud завдяки технології єдиного входу. Спростіть керування доступом і покращте взаємодію користувачів зі своїм продуктом." @@ -2645,7 +2645,7 @@ uk: virus_scanning: description: "Переконайтеся, що вивантажені в OpenProject файли перевіряються на віруси перед тим, як стають доступними для інших користувачів." project_creation_wizard: - description: "Generate a step-by-step wizard to help project managers fill out a project initiation request." + description: "Створіть покроковий майстер, який допоможе менеджерам проєктів заповнити запит на ініціювання проєкту." placeholder_users: title: Прототипи користувачів description: > @@ -2947,15 +2947,15 @@ uk: #We need to include the version to invalidate outdated translations in other locales "17_1": new_features_title: > - The release contains various new features and improvements, such as: + Цей випуск містить різноманітні нові функції та покращення, такі як: new_features_list: - line_0: Automated project initiation (Enterprise add-on). - line_1: "Meetings: add new or existing work packages as outcomes." - line_2: "Meetings: show participant responses in iCal subscriptions." - line_3: "Recurring meetings: duplicate agenda items to the next occurrence." - line_4: "Release to Community: Attribute highlighting." - line_5: Warning before opening external links in user-provided content (Enterprise add-on). - line_6: Improved performance and user experience, including the Activity tab and Documents module. + line_0: Автоматизована ініціація проектів (надбудова Enterprise). + line_1: "Наради: додайте нові або наявні робочі пакети як результати." + line_2: "Наради: показуйте відповіді учасників у підписках iCal." + line_3: "Повторювані наради: копіюйте пункти порядку денного в наступну нараду." + line_4: "Випуск Community: виділення атрибутів." + line_5: Попередження перед відкриттям зовнішніх посилань у контенті, створеному користувачем (надбудова Enterprise). + line_6: Покращено продуктивність і взаємодію з користувачем, зокрема на вкладці «Діяльність» і в модулі «Документи». links: upgrade_enterprise_edition: "Оновлення до версії Enterprise" postgres_migration: "Міграція інсталяції в PostgreSQL" @@ -3023,17 +3023,17 @@ uk: instructions_after_error: "Ви можете спробувати ввійти знову, натиснувши %{signin}. Якщо помилка не зникне, зверніться до адміністратора за допомогою." menus: admin: - ai: "Artificial Intelligence (AI)" + ai: "Штучний інтелект (ШІ)" aggregation: "Агрегація" api_and_webhooks: "API та вебгуки" mail_notification: "Email сповіщення" mails_and_notifications: "Електронні листи й сповіщення" - mcp_configurations: "Model Context Protocol (MCP)" + mcp_configurations: "Модель контекстного протоколу (MCP)" quick_add: label: "Додати…" my_account: access_tokens: - description: "Provider tokens are issued by OpenProject, allowing other applications to access it. Client tokens are issued by other applications, allowing OpenProject to access them." + description: "Маркери постачальника послуг випускаються в OpenProject, що дає змогу іншим програмам отримувати до них доступ. Клієнтські маркери випускаються іншими програмами, що дає змогу OpenProject отримати до них доступ." no_results: title: "Немає маркерів доступу для відображення" description: "Всі вони були відключені. Їх можна повторно ввімкнути в меню адміністрування." @@ -3045,53 +3045,53 @@ uk: simple_revoke_confirmation: "Справі відкликати цей маркер?" tabs: client: - title: "Client tokens" + title: "Клієнтські маркери" provider: - title: "Provider tokens" + title: "Маркери постачальника послуг" token/api: - blank_description: "There is no API token yet. You can create one using the button below." - blank_title: "No API token" + blank_description: "У вас ще немає маркера API. Ви можете створити його за допомогою кнопки нижче." + blank_title: "Немає маркера API" title: "API" - table_title: "API tokens" + table_title: "Маркери API" text_hint: "Маркери API дають змогу стороннім додаткам обмінюватися даними із цим екземпляром OpenProject через інтерфейси REST API." - static_token_name: "API token" + static_token_name: "Маркер API" disabled_text: "Маркери API не дозволено адміністратором. Зверніться до нього, якщо вам потрібна ця функція." - add_button: "API token" + add_button: "Маркер API" ical: - blank_description: "To add an iCalendar token, subscribe to a new or existing calendar from within the Calendar module of a project. You must have the necessary permissions." - blank_title: "No iCalendar token" + blank_description: "Щоб додати маркер iCalendar, підпишіться на новий або наявний календар у модулі календаря проєкту. У вас мають бути необхідні дозволи." + blank_title: "Немає маркера iCalendar" title: "iCalendar" - table_title: "iCalendar tokens" + table_title: "Маркери iCalendar" text_hint_link: "Маркери iCalendar дають змогу користувачам [підписуватися на календарі OpenProject](docs_url) і переглядати актуальну інформацію про пакети робіт у зовнішніх клієнтах." disabled_text: "Підписки на iCalendar не дозволено адміністратором. Зверніться до нього, якщо вам потрібна ця функція." oauth_application: - active_tokens: "Active tokens" - blank_description: "There is no third-party application access configured and active for you." - blank_title: "No OAuth application token" - last_used_at: "Last used at" + active_tokens: "Активні маркери" + blank_description: "Для вас не налаштовано й не активовано доступ до сторонніх додатків." + blank_title: "Немає маркера додатка OAuth" + last_used_at: "Востаннє використано о" title: "OAuth" - table_title: "OAuth application tokens" - text_hint: "OAuth application tokens allow third-party applications to connect with this OpenProject instance." + table_title: "Маркери додатків OAuth" + text_hint: "Маркери додатків OAuth дають змогу стороннім додаткам обмінюватися даними із цим екземпляром OpenProject." oauth_client: - blank_description: "There are no OAuth client tokens yet." - blank_title: "No OAuth client tokens" - failed: "An error occurred and the token couldn't be removed. Please try again later." - integration_type: "Integration type" - table_title: "OAuth client tokens" - text_hint: "OAuth client tokens allow this OpenProject instance to connect with external applications, such as file storages." + blank_description: "Клієнтських маркерів OAuth ще не існує." + blank_title: "Немає клієнтських маркерів OAuth" + failed: "Сталася помилка й не вдалося вилучити маркер. Повторіть спробу пізніше." + integration_type: "Тип інтеграції" + table_title: "Клієнтські маркери OAuth" + text_hint: "Клієнтські маркери OAuth дають змогу цьому екземпляру OpenProject підключатися до зовнішніх додатків, таких як файлові сховища." title: "OAuth" - remove_token: "Do you really want to remove this token? You will need to login again on %{integration}." - removed: "OAuth client token successfully removed" - unknown_integration: "Unknown" + remove_token: "Справді вилучити цей маркер? Вам потрібно буде знову ввійти в %{integration}." + removed: "Клієнтський маркер OAuth успішно вилучено" + unknown_integration: "Невідомо" token/rss: - add_button: "RSS token" - blank_description: "There is no RSS token yet. You can create one using the button below." - blank_title: "No RSS token" + add_button: "Маркер RSS" + blank_description: "У вас ще немає маркера API. Ви можете створити його за допомогою кнопки нижче." + blank_title: "Немає маркера API" title: "RSS" - table_title: "RSS tokens" - text_hint: "RSS tokens allow users to keep up with the latest changes in this OpenProject instance via an external RSS reader." - static_token_name: "RSS token" - disabled_text: "RSS tokens are not enabled by the administrator. Please contact your administrator to use this feature." + table_title: "Маркери RSS" + text_hint: "Маркери RSS дають змогу користувачам зберігати останні зміни в цьому екземплярі OpenProject через зовнішній RSS-агрегатор." + static_token_name: "Маркер RSS" + disabled_text: "Маркери API не дозволено адміністратором. Зверніться до нього, якщо вам потрібна ця функція." storages: unknown_storage: "Невідоме сховище" notifications: @@ -3409,7 +3409,7 @@ uk: label_journal_diff: "Опис Порівняння" label_language: "Мова" label_languages: "Мови" - label_external_links: "External links" + label_external_links: "Зовнішні посилання" label_locale: "Мова й регіон" label_jump_to_a_project: "Перейти до проекту..." label_keyword_plural: "Ключові слова" @@ -4405,9 +4405,9 @@ uk: setting_allowed_link_protocols: "Дозволені протоколи для посилань" setting_allowed_link_protocols_text_html: >- Дозвольте відображення цих протоколів у вигляді посилань в описах пакетів робіт, довгих текстових полях і коментарях. Наприклад, %{tel_code} або %{element_code}. Вводьте по одному протоколу в рядок.
    Протоколи %{http_code}, %{https_code} і %{mailto_code} завжди дозволені. - setting_capture_external_links: "Capture external links" + setting_capture_external_links: "Захоплення зовнішніх посилань" setting_capture_external_links_text: > - When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + Якщо ввімкнено, усі зовнішні посилання у відформатованому тексті переспрямовуватимуть на попереджувальну сторінку перед тим, як вийти з програми. Це допомагає захистити користувачів від потенційно шкідливих зовнішніх вебсайтів. setting_after_first_login_redirect_url: "Переспрямування після першого входу" setting_after_first_login_redirect_url_text_html: > Задайте шлях для переспрямування користувачів після першого входу. Якщо не задано, користувачі переспрямовуються на головну сторінку з ознайомленням.
    Наприклад: /my/page @@ -4421,16 +4421,16 @@ uk: Якщо CORS увімкнено, це джерела, які можуть отримувати доступ до OpenProject API.
    Щоб дізнатися, як указати очікувані значення, ознайомтеся з Документацією щодо заголовка джерела. setting_apiv3_write_readonly_attributes: "Дозвіл для записування атрибутів лише для читання" setting_apiv3_write_readonly_attributes_instructions: > - If enabled, the API will allow administrators to write static read-only attributes during creation, such as createdAt and author. + Якщо ввімкнено, адміністраторам зможуть за допомогою API записувати статичні атрибути тільки для читання під час створення, такі як createdAt та author. setting_apiv3_write_readonly_attributes_warning: > - This setting has a use-case for e.g., importing data, but allows administrators to impersonate the creation of items as other users. All creation requests are being logged however with the true author. + Це налаштування використовується, наприклад, для імпорту даних, але дає змогу адміністраторам створювати елементів від імені інших користувачів. Усі запити на створення реєструються, однак, із зазначенням справжнього автора. setting_apiv3_write_readonly_attributes_additional: > - For more information on attributes and supported resources, please see the %{api_documentation_link}. + Щоб дізнатися більше про атрибути й підтримувані ресурси, відвідайте %{api_documentation_link}. setting_apiv3_max_page_size: "Максимальний розмір сторінки API" setting_apiv3_max_page_size_instructions: > - Set the maximum page size the API will respond with. It will not be possible to perform API requests that return more values on a single page. + Установіть максимальний розмір сторінки, яку повертатиме API. Виконувати запити до API, які повертають більше значень на одній сторінці, буде неможливо. setting_apiv3_max_page_size_warning: > - Please only change this value if you are sure why you need it. Setting to a high value will result in significant performance impacts, while a value lower than the per page options will cause errors in paginated views. + Змінюйте це значення, лише якщо впевнені, навіщо це вам потрібно. Встановлення високого значення призведе до суттєвого впливу на продуктивність, тоді як значення, нижче за параметри на сторінки, призведе до помилок у посторінкових поданнях. setting_apiv3_docs: "Документація" setting_apiv3_docs_enabled: "Увімкнути сторінку документів" setting_apiv3_docs_enabled_instructions_html: > @@ -4615,7 +4615,7 @@ uk: omniauth_direct_login_hint_html: > Якщо цей параметр увімкнено, запити на вхід переспрямовуватимуться налаштованому постачальнику omniauth. Спадний список і сторінку входу буде вимкнено.
    Примітка. Якщо цей параметр увімкнено й вхід за допомогою пароля дозволено, користувачі зможуть входити зі сторінки входу %{internal_path}. remapping_existing_users_hint: > - If enabled, allows any configured identity provider to login existing users based on their username, even if the user never signed in through that provider before. This can be useful when migrating the OpenProject instance to a new SSO provider, but is not recommended when using a provider that is not trusted by all users of your instance. + Якщо ввімкнено, будь-який налаштований постачальник ідентифікаційних даних зможе виконувати вхід в облікові записи наявних користувачів за допомогою їхніх електронних адрес, навіть якщо користувач ніколи раніше не входив через цього постачальника. Це може бути корисно під час перенесення екземпляра OpenProject у сервіс нового постачальника SSO, але не рекомендовано, якщо використовується постачальник, якому не довіряють усі користувачі вашого екземпляра. attachments: whitelist_text_html: > Визначте список дійсних розширень файлів і/або типів MIME для завантажених файлів.
    Введіть розширення файлу (напр., %{ext_example}) або типи MIME (напр., %{mime_example}). Не вказуйте нічого, щоб дозволити завантаження будь-яких типів файлів. Ви можете вводити кілька значень (по одному в кожному рядку). @@ -4721,7 +4721,7 @@ uk: project_mandate: "Мандат проєкту" submission: description_template: > - **This work package was automatically created upon completion of the %{wizard_name} workflow.** A PDF artifact containing all submitted information has been generated and attached to this work package for reference and audit purposes. If you need to update or re-run the initiation steps, you can reopen the wizard at any time by using the link below: + **Цей пакет робіт автоматично створено після завершення робочого процесу «%{wizard_name}».** Артефакт у форматі PDF, що містить усю подану інформацію, було створено й додано в цей пакет робіт для довідки й аудиту. Якщо знадобиться оновити дані або повторити кроки ініціювання, можна будь-коли знову відкрити майстер, скориставшись посиланням, наведеним нижче: description: "Коли користувач подає запит на ініціювання проєкту, створюється новий пакет робіт з артефактом запиту, долученим як файл PDF. Наведені нижче налаштування визначають тип, статус і виконавця нового пакета робіт." work_package_type: "Тип пакета робіт" work_package_type_caption: "Тип пакета робіт, який слід використовувати для зберігання завершеного артефакту." @@ -4982,8 +4982,8 @@ uk: other: "тимчасово заблоковано (%{count} спроби входу в систему)" confirm_status_change: "Ви збираєтеся змінити статус %{name} Ви дійсно бажаєте продовжити?" deleted: "Видалити користувача" - error_status_change_self: "You cannot change your own user status." - error_admin_change_on_non_admin: "Only administrators can change the status of administrator users." + error_status_change_self: "Ви не можете змінити власний статус користувача." + error_admin_change_on_non_admin: "Тільки адміністратори можуть змінювати статус користувачів-адміністраторів." error_status_change_failed: "Не вдалося змінити статус користувача через такі помилки: %{errors}" invite: Запросити користувача електронною поштою invited: запрошені @@ -5390,7 +5390,7 @@ uk: hashed_token: display_value_placeholder: "***" external_link_warning: - title: "Leaving OpenProject" - warning_message: "You are about to leave OpenProject and visit an external website. Please be aware that external websites are not under our control and may have different privacy and security policies." - continue_message: "Are you sure you want to proceed to the following external link?" - continue_button: "Continue to external website" + title: "Вихід з OpenProject" + warning_message: "Ви збираєтеся перейти з OpenProject на зовнішній вебсайт. Зауважте, що ми не контролюємо зовнішні вебсайти, і вони можуть мати іншу політику конфіденційності й безпеки." + continue_message: "Справді перейти за зовнішнім посиланням нижче?" + continue_button: "Перейти на зовнішній вебсайт" diff --git a/config/locales/crowdin/zh-CN.yml b/config/locales/crowdin/zh-CN.yml index 1c5316d29fe..3f7e879d573 100644 --- a/config/locales/crowdin/zh-CN.yml +++ b/config/locales/crowdin/zh-CN.yml @@ -114,10 +114,10 @@ zh-CN: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." - resources_submit: "Update resources" + resources_submit: "更新资源" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." - tools_submit: "Update tools" + tools_submit: "更新工具" multi_update: success: "MCP configurations were updated successfully." server_form: @@ -2929,24 +2929,24 @@ zh-CN: text_hint: "OAuth application tokens allow third-party applications to connect with this OpenProject instance." oauth_client: blank_description: "There are no OAuth client tokens yet." - blank_title: "No OAuth client tokens" - failed: "An error occurred and the token couldn't be removed. Please try again later." - integration_type: "Integration type" - table_title: "OAuth client tokens" - text_hint: "OAuth client tokens allow this OpenProject instance to connect with external applications, such as file storages." + blank_title: "无 OAuth 客户端令牌" + failed: "出错了,无法移除令牌。请稍后再试。" + integration_type: "集成类型" + table_title: "OAuth 客户端令牌" + text_hint: "OAuth 客户端令牌允许此 OpenProject 实例与文件存储等外部应用程序进行连接。" title: "OAuth" - remove_token: "Do you really want to remove this token? You will need to login again on %{integration}." - removed: "OAuth client token successfully removed" - unknown_integration: "Unknown" + remove_token: "是否确实要移除此令牌?您需要再次登录 %{integration}。" + removed: "OAuth 客户端令牌已成功移除" + unknown_integration: "未知" token/rss: add_button: "RSS token" - blank_description: "There is no RSS token yet. You can create one using the button below." - blank_title: "No RSS token" + blank_description: "目前还没有 RSS 标记。您可以使用下面的按钮创建一个。" + blank_title: "无 RSS 令牌" title: "RSS" - table_title: "RSS tokens" - text_hint: "RSS tokens allow users to keep up with the latest changes in this OpenProject instance via an external RSS reader." - static_token_name: "RSS token" - disabled_text: "RSS tokens are not enabled by the administrator. Please contact your administrator to use this feature." + table_title: "RSS 令牌" + text_hint: "RSS 令牌允许用户通过外部 RSS 阅读器了解此 OpenProject 实例中的最新变化。" + static_token_name: "RSS 令牌" + disabled_text: "管理员未启用 RSS 令牌。请联系管理员以使用此功能。" storages: unknown_storage: "未知存储" notifications: @@ -3264,7 +3264,7 @@ zh-CN: label_journal_diff: "描述的对比" label_language: "语言" label_languages: "语言" - label_external_links: "External links" + label_external_links: "外部链接" label_locale: "语言和地区" label_jump_to_a_project: "跳转到一个项目..." label_keyword_plural: "关键词" @@ -4251,7 +4251,7 @@ zh-CN: setting_allowed_link_protocols: "允许的链接协议" setting_allowed_link_protocols_text_html: >- 允许这些协议在工作包描述、长文本字段和评论中显示为链接。例如,%{tel_code} 或 %{element_code}。每行输入一个协议。
    协议 %{http_code}、%{https_code} 和 %{mailto_code} 始终允许。 - setting_capture_external_links: "Capture external links" + setting_capture_external_links: "捕获外部链接" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. setting_after_first_login_redirect_url: "首次登录重定向" diff --git a/config/locales/crowdin/zh-TW.yml b/config/locales/crowdin/zh-TW.yml index a2b7743eb63..4d2ee69bcea 100644 --- a/config/locales/crowdin/zh-TW.yml +++ b/config/locales/crowdin/zh-TW.yml @@ -2802,15 +2802,15 @@ zh-TW: #We need to include the version to invalidate outdated translations in other locales "17_1": new_features_title: > - The release contains various new features and improvements, such as: + 該版本包含多種新功能和改進,例如: new_features_list: - line_0: Automated project initiation (Enterprise add-on). - line_1: "Meetings: add new or existing work packages as outcomes." - line_2: "Meetings: show participant responses in iCal subscriptions." - line_3: "Recurring meetings: duplicate agenda items to the next occurrence." - line_4: "Release to Community: Attribute highlighting." - line_5: Warning before opening external links in user-provided content (Enterprise add-on). - line_6: Improved performance and user experience, including the Activity tab and Documents module. + line_0: 自動化專案啟動(企業附加元件)。 + line_1: "會議:添加新的或現有的工作套件作為成果。" + line_2: "會議:在 iCal 訂閱中顯示參與者的回應。" + line_3: "重複性會議:將議程項目重複至下次會議。" + line_4: "發佈到社群:高亮顯示屬性。" + line_5: 在使用者提供的內容中開啟外部連結前警告(企業附加元件)。 + line_6: 改善效能和使用者體驗,包括「活動」標籤和「文件」模組。 links: upgrade_enterprise_edition: "升級到企業版" postgres_migration: "將您的安裝遷移到 PostgreSQL" @@ -2911,7 +2911,7 @@ zh-TW: text_hint: "API 令牌(Tokens)允許第三方應用程式透過 REST API 與此 OpenProject 實例進行通訊。" static_token_name: "API 令牌(Token)" disabled_text: "API 令牌(Tokens)未由管理員啟用。若要使用此功能,請聯絡您的管理員。" - add_button: "API token" + add_button: "API 令牌(Token)" ical: blank_description: "若要新增 iCalendar 令牌,請從專案的行事曆模組中訂閱新的或現有的行事曆。您必須擁有必要的權限。" blank_title: "沒有 iCalendar 令牌" @@ -2939,7 +2939,7 @@ zh-TW: removed: "成功移除 OAuth 用戶端令牌" unknown_integration: "未知" token/rss: - add_button: "RSS token" + add_button: "RSS 令牌(Token)" blank_description: "目前尚未有 RSS 令牌。您可以使用下面的按鈕建立一個。" blank_title: "沒有 RSS 令牌" title: "RSS" diff --git a/modules/documents/config/locales/crowdin/fr.yml b/modules/documents/config/locales/crowdin/fr.yml index 3625cf01506..0d22673922d 100644 --- a/modules/documents/config/locales/crowdin/fr.yml +++ b/modules/documents/config/locales/crowdin/fr.yml @@ -74,7 +74,7 @@ fr: action: Réessayer connection_recovery_notice: description: "La connexion au serveur de collaboration textuelle en temps réel a été rétablie." - tabs: "Document tabs" + tabs: "Onglets du document" index_page: name: "Nom" type: "Type" diff --git a/modules/documents/config/locales/crowdin/ko.yml b/modules/documents/config/locales/crowdin/ko.yml index 87721519ac6..5f602721b00 100644 --- a/modules/documents/config/locales/crowdin/ko.yml +++ b/modules/documents/config/locales/crowdin/ko.yml @@ -74,7 +74,7 @@ ko: action: 다시 시도 connection_recovery_notice: description: "실시간 텍스트 공동 작업 서버에 대한 연결이 복원되었습니다." - tabs: "Document tabs" + tabs: "문서 탭" index_page: name: "이름" type: "타입" diff --git a/modules/documents/config/locales/crowdin/pt-BR.yml b/modules/documents/config/locales/crowdin/pt-BR.yml index 1fa0930f43b..66c193ff7f0 100644 --- a/modules/documents/config/locales/crowdin/pt-BR.yml +++ b/modules/documents/config/locales/crowdin/pt-BR.yml @@ -74,7 +74,7 @@ pt-BR: action: Tente novamente connection_recovery_notice: description: "A conexão para o servidor de colaboração de texto em tempo real foi restaurada." - tabs: "Document tabs" + tabs: "Abas de documento" index_page: name: "Nome" type: "Tipo" diff --git a/modules/documents/config/locales/crowdin/pt-PT.yml b/modules/documents/config/locales/crowdin/pt-PT.yml index 73b567be360..8d45826e887 100644 --- a/modules/documents/config/locales/crowdin/pt-PT.yml +++ b/modules/documents/config/locales/crowdin/pt-PT.yml @@ -74,7 +74,7 @@ pt-PT: action: Tentar novamente connection_recovery_notice: description: "A ligação ao servidor de colaboração de texto em tempo real foi restaurada." - tabs: "Document tabs" + tabs: "Separadores de documentos" index_page: name: "Nome" type: "Tipo" diff --git a/modules/documents/config/locales/crowdin/uk.yml b/modules/documents/config/locales/crowdin/uk.yml index 5b87f31a0fc..95fa167a9f7 100644 --- a/modules/documents/config/locales/crowdin/uk.yml +++ b/modules/documents/config/locales/crowdin/uk.yml @@ -74,7 +74,7 @@ uk: action: Спробуйте знову connection_recovery_notice: description: "З’єднання з сервером спільного редагування в реальному часі відновлено." - tabs: "Document tabs" + tabs: "Вкладки документів" index_page: name: "Назва" type: "Тип" diff --git a/modules/meeting/config/locales/crowdin/fr.yml b/modules/meeting/config/locales/crowdin/fr.yml index c28af9442bf..d8a67f2fd6a 100644 --- a/modules/meeting/config/locales/crowdin/fr.yml +++ b/modules/meeting/config/locales/crowdin/fr.yml @@ -542,8 +542,8 @@ fr: label_agenda_outcome_actions: "Actions de résultat de l'agenda" label_agenda_outcome_edit: "Modifier le résultat" label_agenda_outcome_delete: "Supprimer le résultat" - label_added_as_outcome: "Added as outcome" - label_write_outcome: "Write outcome" + label_added_as_outcome: "Ajouté comme résultat" + label_write_outcome: "Résultat de l'écriture" label_existing_work_package: "Existing work package" text_outcome_not_editable_anymore: "Ce résultat n'est plus modifiable." text_outcome_cannot_be_added: "Un résultat ne peut plus être ajouté." diff --git a/modules/meeting/config/locales/crowdin/ko.yml b/modules/meeting/config/locales/crowdin/ko.yml index 3d43722b9e6..0e288e69b36 100644 --- a/modules/meeting/config/locales/crowdin/ko.yml +++ b/modules/meeting/config/locales/crowdin/ko.yml @@ -231,15 +231,15 @@ ko: summary: "'%{title}'을(를) %{actor} 님이 취소했습니다." date_time: "스케줄링된 날짜/시간" participant_added: - header: "Meeting '%{title}' - Participant added" - header_series: "Meeting series '%{title}' - Participant added" - summary: "%{actor} added %{participant} to the meeting '%{title}'" - summary_series: "%{actor} added %{participant} to the meeting series '%{title}'" + header: "미팅 '%{title}' - 참가자 추가됨" + header_series: "미팅 시리즈 '%{title}' - 참가자 추가됨" + summary: "%{actor} 님이 미팅 '%{title}'에 %{participant} 님을 추가했습니다" + summary_series: "%{actor} 님이 미팅 시리즈 '%{title}'에 %{participant} 님을 추가했습니다" participant_removed: - header: "Meeting '%{title}' - Participant removed" - header_series: "Meeting series '%{title}' - Participant removed" - summary: "%{actor} removed %{participant} from the meeting '%{title}'" - summary_series: "%{actor} removed %{participant} from the meeting series '%{title}'" + header: "미팅 '%{title}' - 참가자 제거됨" + header_series: "미팅 시리즈 '%{title}' - 참가자 제거됨" + summary: "%{actor} 님이 미팅 '%{title}'에서 %{participant} 님을 제거했습니다" + summary_series: "%{actor} 님이 미팅 시리즈 '%{title}'에서 %{participant} 님을 제거했습니다" ended: header_series: "종료됨: 미팅 시리즈 '%{title}'" summary_series: "'%{title}' 미팅 시리즈를 %{actor} 님이 종료했습니다." @@ -535,9 +535,9 @@ ko: label_agenda_outcome_actions: "의제 결과 작업" label_agenda_outcome_edit: "결과 편집" label_agenda_outcome_delete: "결과 제거" - label_added_as_outcome: "Added as outcome" - label_write_outcome: "Write outcome" - label_existing_work_package: "Existing work package" + label_added_as_outcome: "결과로 추가됨" + label_write_outcome: "결과 작성" + label_existing_work_package: "기존 작업 패키지" text_outcome_not_editable_anymore: "이 결과는 더 이상 편집할 수 없습니다." text_outcome_cannot_be_added: "더 이상 결과를 추가할 수 없습니다." label_backlog_clear: "백로그 지우기" diff --git a/modules/meeting/config/locales/crowdin/pt-BR.yml b/modules/meeting/config/locales/crowdin/pt-BR.yml index 5ee7d463497..c0a09d0cf4a 100644 --- a/modules/meeting/config/locales/crowdin/pt-BR.yml +++ b/modules/meeting/config/locales/crowdin/pt-BR.yml @@ -236,15 +236,15 @@ pt-BR: summary: "\"%{title}\" foi cancelada por %{actor}." date_time: "Data/hora programada" participant_added: - header: "Meeting '%{title}' - Participant added" - header_series: "Meeting series '%{title}' - Participant added" - summary: "%{actor} added %{participant} to the meeting '%{title}'" - summary_series: "%{actor} added %{participant} to the meeting series '%{title}'" + header: "Reunião \"%{title}\" - Participante adicionado" + header_series: "Série de reuniões \"%{title}\" - Participante adicionado" + summary: "%{actor} adicionou %{participant} à reunião \"%{title}\"" + summary_series: "%{actor} adicionou %{participant} à série de reuniões \"%{title}\"" participant_removed: - header: "Meeting '%{title}' - Participant removed" - header_series: "Meeting series '%{title}' - Participant removed" - summary: "%{actor} removed %{participant} from the meeting '%{title}'" - summary_series: "%{actor} removed %{participant} from the meeting series '%{title}'" + header: "Reunião \"%{title}\" - Participante removido" + header_series: "Série de reuniões \"%{title}\" - Participante removido" + summary: "%{actor} removeu %{participant} da reunião \"%{title}\"" + summary_series: "%{actor} removeu %{participant} da série de reuniões \"%{title}\"" ended: header_series: "Encerrada: série de reuniões \"%{title}\"" summary_series: "A série de reuniões \"%{title}\" foi encerrada por %{actor}." @@ -542,7 +542,7 @@ pt-BR: label_agenda_outcome_actions: "Ações resultantes da pauta" label_agenda_outcome_edit: "Editar resultado" label_agenda_outcome_delete: "Remover resultado" - label_added_as_outcome: "Added as outcome" + label_added_as_outcome: "Adicionado como resultado" label_write_outcome: "Write outcome" label_existing_work_package: "Existing work package" text_outcome_not_editable_anymore: "Este resultado não pode mais ser editado." diff --git a/modules/meeting/config/locales/crowdin/pt-PT.yml b/modules/meeting/config/locales/crowdin/pt-PT.yml index 4631072b60a..ea50ff6ad31 100644 --- a/modules/meeting/config/locales/crowdin/pt-PT.yml +++ b/modules/meeting/config/locales/crowdin/pt-PT.yml @@ -236,15 +236,15 @@ pt-PT: summary: "\"%{title}\" foi cancelada por %{actor}." date_time: "Data/hora programada" participant_added: - header: "Meeting '%{title}' - Participant added" - header_series: "Meeting series '%{title}' - Participant added" - summary: "%{actor} added %{participant} to the meeting '%{title}'" - summary_series: "%{actor} added %{participant} to the meeting series '%{title}'" + header: "Reunião '%{title}' - Participante adicionado" + header_series: "Série de reuniões '%{title}' - Participante adicionado" + summary: "%{actor} adicionou %{participant} à reunião '%{title}'" + summary_series: "%{actor} adicionou %{participant} à série de reuniões '%{title}'" participant_removed: - header: "Meeting '%{title}' - Participant removed" - header_series: "Meeting series '%{title}' - Participant removed" - summary: "%{actor} removed %{participant} from the meeting '%{title}'" - summary_series: "%{actor} removed %{participant} from the meeting series '%{title}'" + header: "Reunião '%{title}' - Participante removido" + header_series: "Série de reuniões '%{title}' - Participante removido" + summary: "%{actor} removeu %{participant} da reunião '%{title}'" + summary_series: "%{actor} removeu %{participant} da série de reuniões '%{title}'" ended: header_series: "Terminada: Série de reuniões \"%{title}\"" summary_series: "A série de reuniões \"%{title}\" foi terminada por %{actor}." @@ -542,9 +542,9 @@ pt-PT: label_agenda_outcome_actions: "Ações dos resultados da agenda" label_agenda_outcome_edit: "Editar resultado" label_agenda_outcome_delete: "Remover resultado" - label_added_as_outcome: "Added as outcome" - label_write_outcome: "Write outcome" - label_existing_work_package: "Existing work package" + label_added_as_outcome: "Adicionado como resultado" + label_write_outcome: "Escrever o resultado" + label_existing_work_package: "Pacote de trabalho existente" text_outcome_not_editable_anymore: "Este resultado já não pode ser editado." text_outcome_cannot_be_added: "Já não é possível adicionar um resultado." label_backlog_clear: "Limpar backlog" @@ -611,9 +611,9 @@ pt-PT: text_agenda_item_duplicate_in_next_meeting: "Tem a certeza de que quer acrescentar uma cópia deste ponto da ordem de trabalhos à próxima reunião, em %{date} em %{time}? Os resultados não serão duplicados." text_agenda_item_duplicated_in_next_meeting: "Ponto da ordem de trabalhos duplicado na próxima reunião, em %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "Este pacote de trabalho ainda não está agendado em uma reunião futura." - text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." - text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." - text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." + text_agenda_item_no_available_occurrence: "Todas as ocorrências futuras foram canceladas." + text_agenda_item_dialog_skipping_cancelled_one: "Nota: A ignorar a ocorrência cancelada em %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Nota: A ignorar %{count} ocorrências canceladas." text_work_package_add_to_meeting_hint: 'Use o botão "Adicionar à reunião" para adicionar este pacote de trabalho a uma reunião futura.' text_work_package_has_no_past_meeting_agenda_items: "Este pacote de trabalho não foi acrescentado como ponto da ordem de trabalhos numa reunião anterior." text_email_updates_muted: "As atualizações do calendário por e-mail estão silenciadas. Os participantes não receberão convites atualizados por e-mail quando fizer alterações." @@ -621,10 +621,10 @@ pt-PT: my_account: access_tokens: token/ical_meeting: - blank_description: "You can create one using the button below." - blank_title: "No iCalendar meeting token" + blank_description: "Pode criar uma com o botão abaixo." + blank_title: "Nenhum token de reunião iCalendar" title: "iCalendar para reuniões" - table_title: "iCalendar meeting tokens" + table_title: "Tokens de reunião iCalendar" text_hint: "Os tokens do iCalendar permitem aos utilizadores subscrever os calendários do OpenProject, e ver informações atualizadas sobre pacotes de trabalho de clientes externos." disabled_text: "As subscrições de reuniões do iCalendar não estão ativadas pelo administrador. Contacte o seu administrador para utilizar esta funcionalidade." add_button: "Subscrever o calendário" diff --git a/modules/meeting/config/locales/crowdin/uk.yml b/modules/meeting/config/locales/crowdin/uk.yml index c091e326e3f..0610bc43c91 100644 --- a/modules/meeting/config/locales/crowdin/uk.yml +++ b/modules/meeting/config/locales/crowdin/uk.yml @@ -246,15 +246,15 @@ uk: summary: "«%{title}» скасував(-ла) %{actor}." date_time: "Заплановані дата/час" participant_added: - header: "Meeting '%{title}' - Participant added" - header_series: "Meeting series '%{title}' - Participant added" - summary: "%{actor} added %{participant} to the meeting '%{title}'" - summary_series: "%{actor} added %{participant} to the meeting series '%{title}'" + header: "Нарада «%{title}» — додано учасника" + header_series: "Серія нарад «%{title}» — додано учасника" + summary: "Виконавець %{actor} додав учасника %{participant} до наради «%{title}»" + summary_series: "Виконавець %{actor} додав учасника %{participant} до серії нарад «%{title}»" participant_removed: - header: "Meeting '%{title}' - Participant removed" - header_series: "Meeting series '%{title}' - Participant removed" - summary: "%{actor} removed %{participant} from the meeting '%{title}'" - summary_series: "%{actor} removed %{participant} from the meeting series '%{title}'" + header: "Нарада «%{title}» — вилучено учасника" + header_series: "Серія нарад «%{title}» — вилучено учасника" + summary: "Виконавець %{actor} вилучив учасника %{participant} із наради «%{title}»" + summary_series: "Виконавець %{actor} вилучив учасника %{participant} із серії нарад «%{title}»" ended: header_series: "Завершено: серія нарад «%{title}»" summary_series: "Серію нарад «%{title}» завершив(-ла) %{actor}." @@ -556,9 +556,9 @@ uk: label_agenda_outcome_actions: "Дії з результатами порядку денного" label_agenda_outcome_edit: "Редагувати результат" label_agenda_outcome_delete: "Вилучити результат" - label_added_as_outcome: "Added as outcome" - label_write_outcome: "Write outcome" - label_existing_work_package: "Existing work package" + label_added_as_outcome: "Додано як результат" + label_write_outcome: "Записати результат" + label_existing_work_package: "Наявний пакет робіт" text_outcome_not_editable_anymore: "Цей результат більше не можна редагувати." text_outcome_cannot_be_added: "Результат більше не можна додати." label_backlog_clear: "Очистити чергу завдань" @@ -625,9 +625,9 @@ uk: text_agenda_item_duplicate_in_next_meeting: "Справді додати копію цього пункту порядку денного в наступну нараду, що відбудеться %{date} о %{time}? Результати не дублюватимуться." text_agenda_item_duplicated_in_next_meeting: "Пункт порядку денного продубльовано в нову нараду, що відбудеться %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "Цей пакет робіт ще не заплановано в порядку денному майбутньої наради." - text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." - text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." - text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." + text_agenda_item_no_available_occurrence: "Усі наступні наради скасовано." + text_agenda_item_dialog_skipping_cancelled_one: "Примітка. Пропуск скасованої наради %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Примітка. Пропуск скасованих нарад (%{count})." text_work_package_add_to_meeting_hint: 'Натисніть кнопку «Додати до наради», щоб додати цей пакет робіт до майбутньої наради.' text_work_package_has_no_past_meeting_agenda_items: "Цей пакет робіт не було додано як пункт порядку денного в минулу нараду." text_email_updates_muted: "Надсилання електронною поштою оновлень із календаря тимчасово вимкнено. Учасники не отримають листи з оновленими запрошеннями після того, як ви внесете зміни." @@ -635,10 +635,10 @@ uk: my_account: access_tokens: token/ical_meeting: - blank_description: "You can create one using the button below." - blank_title: "No iCalendar meeting token" + blank_description: "Ви можете створити нараду, натиснувши кнопку нижче." + blank_title: "Немає маркера наради в iCalendar" title: "iCalendar для нарад" - table_title: "iCalendar meeting tokens" + table_title: "Маркери нарад в iCalendar" text_hint: "Маркери iCalendar для нарад дають змогу користувачам підписуватися на всі свої наради й переглядати актуальну інформацію про них у зовнішніх клієнтах." disabled_text: "Підписки на наради iCalendar не активував адміністратор. Зв’яжіться з ним, щоб використовувати цю функцію." add_button: "Підписатися на календар" diff --git a/modules/meeting/config/locales/crowdin/zh-TW.yml b/modules/meeting/config/locales/crowdin/zh-TW.yml index d1ecac26df1..27154692911 100644 --- a/modules/meeting/config/locales/crowdin/zh-TW.yml +++ b/modules/meeting/config/locales/crowdin/zh-TW.yml @@ -604,9 +604,9 @@ zh-TW: text_agenda_item_duplicate_in_next_meeting: "您確定要在下次會議中加入此議程項目的副本, %{date} ,網址為 %{time}? 結果不會重複。" text_agenda_item_duplicated_in_next_meeting: "與下次會議重複的議程項目, %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "該工作套件尚未被安排到即將舉行的會議議程中。" - text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." - text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." - text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." + text_agenda_item_no_available_occurrence: "所有即將舉行的活動均已取消。" + text_agenda_item_dialog_skipping_cancelled_one: "備註:已跳過 %{date} 的取消項目。" + text_agenda_item_dialog_skipping_cancelled_many: "備註:略過 %{count} 個已取消的發生項目。" text_work_package_add_to_meeting_hint: '使用"增加到會議"按鈕將此工作套件新增到即將舉行的會議。' text_work_package_has_no_past_meeting_agenda_items: "此工作套件未被新增為過去會議中的議程項目。" text_email_updates_muted: "將停止電子郵件通知「行事曆更新」。當您變更時,參與者將不會透過電子郵件收到更新的邀請。" diff --git a/modules/storages/config/locales/crowdin/pt-PT.yml b/modules/storages/config/locales/crowdin/pt-PT.yml index 7fd08d404ee..c19fcc03323 100644 --- a/modules/storages/config/locales/crowdin/pt-PT.yml +++ b/modules/storages/config/locales/crowdin/pt-PT.yml @@ -104,20 +104,20 @@ pt-PT: create_folder: 'Criação de pastas de projeto geridas:' ensure_root_folder_permissions: 'Definir permissões da pasta base:' hide_inactive_folders: 'Passo "Ocultar pastas inativas":' - remote_folders: 'Read contents of the team folder:' + remote_folders: 'Ler conteúdo da pasta de equipa:' remove_user_from_group: 'Remover utilizador do grupo:' rename_project_folder: 'Renomear pasta do projeto gerido:' one_drive_sync_service: create_folder: 'Criação de pastas de projeto geridas:' ensure_root_folder_permissions: 'Definir permissões da pasta base:' hide_inactive_folders: 'Passo "Ocultar pastas inativas":' - remote_folders: 'Read contents of the drive root folder:' + remote_folders: 'Ler conteúdo da pasta raiz da unidade:' rename_project_folder: 'Renomear pasta do projeto gerido:' sharepoint_sync_service: create_folder: 'Criação de pastas de projeto geridas:' ensure_root_folder_permissions: 'Definir permissões da pasta base:' hide_inactive_folders: 'Passo "Ocultar pastas inativas":' - remote_folders: 'Read contents of the drive root folder:' + remote_folders: 'Ler conteúdo da pasta raiz da unidade:' rename_project_folder: 'Renomear pasta do projeto gerido:' errors: messages: @@ -140,7 +140,7 @@ pt-PT: conflict: A pasta %{folder_name} já existe em %{parent_location}. not_found: "%{parent_location} não foi encontrado." ensure_root_folder_permissions: - not_found: "%{group_folder} wasn't found. Please check your Nextcloud Team Folder setup." + not_found: "%{group_folder} não foi encontrado. Verifique a configuração da pasta de equipa do Nextcloud." permission_not_set: não foi possível definir permissões em %{group_folder}. hide_inactive_folders: permission_not_set: não foi possível definir permissões em %{path}. @@ -230,7 +230,7 @@ pt-PT: storage_delete_result_3: A pasta do projeto gerida automaticamente e todos os ficheiros nela contidos serão eliminados dependencies: nextcloud: - group_folders_app: Team Folders + group_folders_app: Pastas de equipa integration_app: Integração do OpenProject enabled_in_projects: setup_incomplete_description: Este armazenamento tem uma configuração incompleta. Conclua a configuração antes de o ativar em vários projetos. @@ -277,11 +277,11 @@ pt-PT: client_folder_creation: Criação automática de pasta client_folder_removal: Eliminação automática de pasta drive_contents: Conteúdo da unidade - files_request: Fetching team folder files + files_request: A obter ficheiros da pasta de equipa header: Pastas do projeto geridas automaticamente - team_folder_app: 'Dependency: Team Folders' - team_folder_contents: Team folder content - team_folder_presence: Team folder exists + team_folder_app: 'Dependência: Pastas de equipa' + team_folder_contents: Conteúdo da pasta de equipa + team_folder_presence: A pasta de equipa existe userless_access: Autenticação da solicitação do lado do servidor authentication: existing_token: Ficha do utilizador @@ -322,8 +322,8 @@ pt-PT: nc_oauth_request_not_found: O terminal para o utilizador com sessão iniciada não foi encontrado. Consulte os registos do servidor para mais informações. nc_oauth_request_unauthorized: O utilizador atual não está autorizado a aceder ao armazenamento de ficheiros remoto. Verifique os registos do servidor para mais informações. nc_oauth_token_missing: O OpenProject não pode testar a comunicação de nível de utilizador com o Nextcloud uma vez que o utilizador ainda não associou a sua conta Nextcloud. - nc_team_folder_not_found: The team folder could not be found. - nc_unexpected_content: Unexpected content found in the managed team folder. + nc_team_folder_not_found: Não foi possível encontrar a pasta de equipa. + nc_unexpected_content: Conteúdo inesperado encontrado na pasta de equipa gerida. nc_userless_access_denied: A palavra-passe da aplicação configurada é inválida. not_configured: Não foi possível validar a ligação.Termine, primeiro, a configuração. od_client_cant_delete_folder: O cliente está a ter problemas a eliminar pastas. Consulte a documentação de configuração para o seu armazenamento. diff --git a/modules/storages/config/locales/crowdin/uk.yml b/modules/storages/config/locales/crowdin/uk.yml index fc5c22aa616..81e6fb1720c 100644 --- a/modules/storages/config/locales/crowdin/uk.yml +++ b/modules/storages/config/locales/crowdin/uk.yml @@ -104,20 +104,20 @@ uk: create_folder: 'Створення керованої папки проєкту' ensure_root_folder_permissions: 'Установіть дозволи для основної папки:' hide_inactive_folders: 'Етап «Приховайте неактивні папки»:' - remote_folders: 'Read contents of the team folder:' + remote_folders: 'Читання вмісту папки групи:' remove_user_from_group: 'Вилучення користувача з групи:' rename_project_folder: 'Перейменування керованої папки проєкту:' one_drive_sync_service: create_folder: 'Створення керованої папки проєкту:' ensure_root_folder_permissions: 'Установлення дозволів для основної папки:' hide_inactive_folders: 'Етап «Приховайте неактивні папки»:' - remote_folders: 'Read contents of the drive root folder:' + remote_folders: 'Читання вмісту кореневої папки диска:' rename_project_folder: 'Перейменування папки керованого проекту:' sharepoint_sync_service: create_folder: 'Створення керованої папки проєкту:' ensure_root_folder_permissions: 'Установлення дозволів для основної папки:' hide_inactive_folders: 'Етап «Приховайте неактивні папки»:' - remote_folders: 'Read contents of the drive root folder:' + remote_folders: 'Читання вмісту кореневої папки диска:' rename_project_folder: 'Перейменування папки керованого проекту:' errors: messages: @@ -140,7 +140,7 @@ uk: conflict: Папка «%{folder_name}» уже існує в розташуванні %{parent_location}. not_found: "%{parent_location} не знайдено." ensure_root_folder_permissions: - not_found: "%{group_folder} wasn't found. Please check your Nextcloud Team Folder setup." + not_found: "%{group_folder} не знайдено. Перевірте конфігурацію своєї папки команди Nextcloud." permission_not_set: не вдалося встановити дозволи в папці «%{group_folder}». hide_inactive_folders: permission_not_set: не вдалося встановити дозволи за шляхом %{path}. @@ -230,7 +230,7 @@ uk: storage_delete_result_3: Буде видалено папку проєкту з автоматичним керуванням і всі файли, що містяться в ній dependencies: nextcloud: - group_folders_app: Team Folders + group_folders_app: Папки команди integration_app: Інтеграція OpenProject enabled_in_projects: setup_incomplete_description: Це сховище ще не налаштоване. Завершіть налаштування, перш ніж вмикати його в кількох проєктах. @@ -277,11 +277,11 @@ uk: client_folder_creation: Автоматичне створення папки client_folder_removal: Автоматичне видалення папки drive_contents: Вміст диска - files_request: Fetching team folder files + files_request: Отримання файлів із папок команди header: Папки проєкту з автоматичним керуванням - team_folder_app: 'Dependency: Team Folders' - team_folder_contents: Team folder content - team_folder_presence: Team folder exists + team_folder_app: 'Залежність: папки команди' + team_folder_contents: Вміст папки команди + team_folder_presence: Папка команди існує userless_access: Автентифікація запиту на стороні сервера authentication: existing_token: Маркер користувача @@ -326,8 +326,8 @@ uk: nc_oauth_request_not_found: Кінцеву точку для отримання підключеного зараз користувача не знайдено. Щоб дізнатися більше, перевірте журнали сервера. nc_oauth_request_unauthorized: Поточний користувач не має дозволу на доступ до віддаленого файлового сховища. Щоб дізнатися більше, перевірте журнали сервера. nc_oauth_token_missing: OpenProject не може перевірити з’єднання на рівні користувача з Nextcloud, оскільки користувач досі не зв’язав свій обліковий запис Nextcloud. - nc_team_folder_not_found: The team folder could not be found. - nc_unexpected_content: Unexpected content found in the managed team folder. + nc_team_folder_not_found: Папку команди не знайдено. + nc_unexpected_content: У папці керованої команди знайдено неочікуваний вміст. nc_userless_access_denied: Налаштований пароль застосунку недійсний. not_configured: Не вдалося перевірити підключення. Спочатку налаштуйте конфігурацію. od_client_cant_delete_folder: Клієнту не вдалося видалити папки. Ознайомтеся з документацією щодо конфігурації для свого сховища. From fd712d18e102148454ce06627467d777b3564ed1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 05:35:22 +0000 Subject: [PATCH 093/293] Bump core-js from 3.47.0 to 3.48.0 in /frontend Bumps [core-js](https://github.com/zloirock/core-js/tree/HEAD/packages/core-js) from 3.47.0 to 3.48.0. - [Release notes](https://github.com/zloirock/core-js/releases) - [Changelog](https://github.com/zloirock/core-js/blob/master/CHANGELOG.md) - [Commits](https://github.com/zloirock/core-js/commits/v3.48.0/packages/core-js) --- updated-dependencies: - dependency-name: core-js dependency-version: 3.48.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 15 +++++++-------- frontend/package.json | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4dc0f2c2dbd..dd102d0b3e1 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -78,7 +78,7 @@ "chartjs-plugin-datalabels": "^2.2.0", "codemirror": "^5.62.0", "copy-text-to-clipboard": "^3.2.2", - "core-js": "^3.47.0", + "core-js": "^3.48.0", "crossvent": "^1.5.4", "dom-autoscroller": "^2.2.8", "dom-plane": "^1.0.2", @@ -12686,11 +12686,10 @@ } }, "node_modules/core-js": { - "version": "3.47.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz", - "integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==", + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz", + "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==", "hasInstallScript": true, - "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/core-js" @@ -33579,9 +33578,9 @@ } }, "core-js": { - "version": "3.47.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz", - "integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==" + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz", + "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==" }, "core-js-compat": { "version": "3.47.0", diff --git a/frontend/package.json b/frontend/package.json index 18a4f4d79f6..4c7a1cc8dc0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -133,7 +133,7 @@ "chartjs-plugin-datalabels": "^2.2.0", "codemirror": "^5.62.0", "copy-text-to-clipboard": "^3.2.2", - "core-js": "^3.47.0", + "core-js": "^3.48.0", "crossvent": "^1.5.4", "dom-autoscroller": "^2.2.8", "dom-plane": "^1.0.2", From 2ab44d2cdfa002e9704d5521d6b370684bebfcc2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 05:35:49 +0000 Subject: [PATCH 094/293] Bump @mantine/core from 8.3.12 to 8.3.13 in /frontend Bumps [@mantine/core](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/core) from 8.3.12 to 8.3.13. - [Release notes](https://github.com/mantinedev/mantine/releases) - [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md) - [Commits](https://github.com/mantinedev/mantine/commits/8.3.13/packages/@mantine/core) --- updated-dependencies: - dependency-name: "@mantine/core" dependency-version: 8.3.13 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 17 ++++++++--------- frontend/package.json | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4dc0f2c2dbd..e32ae6ab2f7 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -49,7 +49,7 @@ "@hotwired/turbo-rails": "^8.0.20", "@knowledgecode/delegate": "^0.10.0", "@kolkov/ngx-gallery": "^2.0.1", - "@mantine/core": "^8.3.10", + "@mantine/core": "^8.3.13", "@mantine/hooks": "^8.3.6", "@mantine/utils": "^6.0.22", "@ng-select/ng-option-highlight": "^20.6.3", @@ -6206,10 +6206,9 @@ } }, "node_modules/@mantine/core": { - "version": "8.3.12", - "resolved": "https://registry.npmjs.org/@mantine/core/-/core-8.3.12.tgz", - "integrity": "sha512-bDEoUl4SneltfI1GeEaBk6BVDbLuB/w15YwseAmUvc8ldAbNcsVhxKxY/BdhwqUo6O3L2vhdlb3WwxR1y8741g==", - "license": "MIT", + "version": "8.3.13", + "resolved": "https://registry.npmjs.org/@mantine/core/-/core-8.3.13.tgz", + "integrity": "sha512-ZgW4vqN4meaPyIMxzAufBvsgmJRfYZdTpsrAOcS8pWy7m9e8i685E7XsAxnwJCOIHudpvpvt+7Bx7VaIjEsYEw==", "dependencies": { "@floating-ui/react": "^0.27.16", "clsx": "^2.1.1", @@ -6219,7 +6218,7 @@ "type-fest": "^4.41.0" }, "peerDependencies": { - "@mantine/hooks": "8.3.12", + "@mantine/hooks": "8.3.13", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" } @@ -29493,9 +29492,9 @@ "integrity": "sha512-EbsszrASgT85GH3B7jkx7YXfQyIYo/rlobwMx6V3ewETapPUwdSAInv+89flnk5n2eu2Lpdeh+2zS6PvqbL2RA==" }, "@mantine/core": { - "version": "8.3.12", - "resolved": "https://registry.npmjs.org/@mantine/core/-/core-8.3.12.tgz", - "integrity": "sha512-bDEoUl4SneltfI1GeEaBk6BVDbLuB/w15YwseAmUvc8ldAbNcsVhxKxY/BdhwqUo6O3L2vhdlb3WwxR1y8741g==", + "version": "8.3.13", + "resolved": "https://registry.npmjs.org/@mantine/core/-/core-8.3.13.tgz", + "integrity": "sha512-ZgW4vqN4meaPyIMxzAufBvsgmJRfYZdTpsrAOcS8pWy7m9e8i685E7XsAxnwJCOIHudpvpvt+7Bx7VaIjEsYEw==", "requires": { "@floating-ui/react": "^0.27.16", "clsx": "^2.1.1", diff --git a/frontend/package.json b/frontend/package.json index 18a4f4d79f6..b85108dc6fc 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -104,7 +104,7 @@ "@hotwired/turbo-rails": "^8.0.20", "@knowledgecode/delegate": "^0.10.0", "@kolkov/ngx-gallery": "^2.0.1", - "@mantine/core": "^8.3.10", + "@mantine/core": "^8.3.13", "@mantine/hooks": "^8.3.6", "@mantine/utils": "^6.0.22", "@ng-select/ng-option-highlight": "^20.6.3", From 4d305df7148e25416b9d83a0422f2e32b0bd0ceb Mon Sep 17 00:00:00 2001 From: Jan Sandbrink Date: Fri, 30 Jan 2026 10:17:25 +0100 Subject: [PATCH 095/293] Allow to use API Tokens as Bearer tokens We generate those tokens with a prefix, so that we can decide by looking at a token, whether it's an API Token or a different kind of token, so that we can decide which code path to choose for validating the token. The usage of access tokens as Bearer token has the usability advantage, that you can paste them as plaintext into tools that expect you to specify the token as a header. Also the Basic auth approach for our old tokens usually rather caused issues, such as browsers prompting for credentials in surprising situations. If we were to deprecate basic authentication one day, this change today could've been the first step towards that. --- app/models/token/api.rb | 1 + app/models/token/auto_login.rb | 2 + app/models/token/backup.rb | 2 + app/models/token/base.rb | 43 ++++++---- app/models/token/hashed_token.rb | 2 +- app/models/token/ical.rb | 2 + config/configuration.yml.example | 2 +- config/initializers/warden.rb | 4 +- docs/api/apiv3/openapi-spec.yml | 40 ++++++--- docs/release-notes/14/14-4-0/README.md | 10 +-- .../strategies/warden/user_api_token.rb | 83 +++++++++++++++++++ spec/models/token/base_token_spec.rb | 14 ++++ spec/requests/api/v3/authentication_spec.rb | 68 +++++++++++++++ spec/requests/mcp/tools_list_spec.rb | 4 +- 14 files changed, 243 insertions(+), 34 deletions(-) create mode 100644 lib_static/open_project/authentication/strategies/warden/user_api_token.rb diff --git a/app/models/token/api.rb b/app/models/token/api.rb index 2a28819405c..fe25f61356b 100644 --- a/app/models/token/api.rb +++ b/app/models/token/api.rb @@ -30,5 +30,6 @@ module Token class API < Named + prefix :opapi end end diff --git a/app/models/token/auto_login.rb b/app/models/token/auto_login.rb index 8e8e83ca1d4..4bf3692209d 100644 --- a/app/models/token/auto_login.rb +++ b/app/models/token/auto_login.rb @@ -32,6 +32,8 @@ module Token class AutoLogin < HashedToken include ExpirableToken + prefix :opal + has_many :autologin_session_links, class_name: "Sessions::AutologinSessionLink", foreign_key: "token_id", diff --git a/app/models/token/backup.rb b/app/models/token/backup.rb index 9d621d234d9..7b418c7dbe0 100644 --- a/app/models/token/backup.rb +++ b/app/models/token/backup.rb @@ -30,6 +30,8 @@ module Token class Backup < HashedToken + prefix :opbk + def ready? return false if created_at.nil? diff --git a/app/models/token/base.rb b/app/models/token/base.rb index b7aa526928d..7ec88b1e9c9 100644 --- a/app/models/token/base.rb +++ b/app/models/token/base.rb @@ -64,22 +64,37 @@ module Token # Delete previous token of this type upon save before_save :delete_previous_token - ## - # Find a token from the token value - def self.find_by_plaintext_value(input) - find_by(value: input) - end + class << self + ## + # A DSL method allowing to define a prefix for all generated tokens, making it possible to recognize + # the purpose of a token by looking at the token value. + # + # class MyToken < HashedToken + # prefix :my + # end + def prefix(value = nil) + @prefix = value.to_s if value - ## - # Find tokens for the given user - def self.for_user(user) - where(user:) - end + @prefix + end - ## - # Generate a random hex token value - def self.generate_token_value - SecureRandom.hex(32) + ## + # Find a token from the token value + def find_by_plaintext_value(input) + find_by(value: input) + end + + ## + # Find tokens for the given user + def for_user(user) + where(user:) + end + + ## + # Generate a random hex token value + def generate_token_value + [prefix, SecureRandom.hex(32)].compact.join("-") + end end ## diff --git a/app/models/token/hashed_token.rb b/app/models/token/hashed_token.rb index 3bcd7d01493..7e1e9699f35 100644 --- a/app/models/token/hashed_token.rb +++ b/app/models/token/hashed_token.rb @@ -53,7 +53,7 @@ module Token class << self def create_and_return_value(user) - create(user:).plain_value + create!(user:).plain_value end ## diff --git a/app/models/token/ical.rb b/app/models/token/ical.rb index 058756aa2ec..9911bd30ae8 100644 --- a/app/models/token/ical.rb +++ b/app/models/token/ical.rb @@ -30,6 +30,8 @@ module Token class ICal < HashedToken + prefix :opical + # restrict the usage of one ical token to one query (calendar) has_one :ical_token_query_assignment, required: true, dependent: :destroy, foreign_key: :ical_token_id, class_name: "ICalTokenQueryAssignment", inverse_of: :ical_token diff --git a/config/configuration.yml.example b/config/configuration.yml.example index adc6bfe86ad..fe4f7b4d9aa 100644 --- a/config/configuration.yml.example +++ b/config/configuration.yml.example @@ -384,7 +384,7 @@ default: # password: admin # By default, the APIv3 allows authentication through basic auth. - # Uncomment the following line to restrict APIv3 access to session. + # Uncomment the following line to prevent APIv3 access using Basic auth. # apiv3_enable_basic_auth: false # You can configure where users should be sent after the login diff --git a/config/initializers/warden.rb b/config/initializers/warden.rb index 0918ad64492..bb715711ba5 100644 --- a/config/initializers/warden.rb +++ b/config/initializers/warden.rb @@ -34,6 +34,7 @@ strategies = [ [:basic_auth_failure, namespace::BasicAuthFailure, "Basic"], [:global_basic_auth, namespace::GlobalBasicAuth, "Basic"], [:user_basic_auth, namespace::UserBasicAuth, "Basic"], + [:user_api_token, namespace::UserAPIToken, "Bearer"], [:oauth, namespace::DoorkeeperOAuth, "Bearer"], [:anonymous_fallback, namespace::AnonymousFallback, "Basic"], [:jwt_oidc, namespace::JwtOidc, "Bearer"], @@ -48,6 +49,7 @@ OpenProject::Authentication.update_strategies(OpenProject::Authentication::Scope %i[global_basic_auth user_basic_auth basic_auth_failure + user_api_token oauth jwt_oidc session @@ -59,7 +61,7 @@ OpenProject::Authentication.update_strategies(OpenProject::Authentication::Scope end OpenProject::Authentication.update_strategies(OpenProject::Authentication::Scope::MCP_SCOPE, { store: false }) do |_| - %i[oauth jwt_oidc user_basic_auth basic_auth_failure] + %i[user_api_token oauth jwt_oidc user_basic_auth basic_auth_failure] end Rails.application.configure do |app| diff --git a/docs/api/apiv3/openapi-spec.yml b/docs/api/apiv3/openapi-spec.yml index 4c92879d312..e5f5bf05c20 100644 --- a/docs/api/apiv3/openapi-spec.yml +++ b/docs/api/apiv3/openapi-spec.yml @@ -49,7 +49,15 @@ info: ## Authentication - The API supports the following authentication schemes: OAuth2, session based authentication, and basic auth. + The API supports the following authentication schemes: + + * Session-based authentication + * API tokens + * passed as Bearer token + * passed via Basic auth + * OAuth 2.0 + * using built-in authorization server + * using an external authorization server (RFC 9068) Depending on the settings of the OpenProject instance many resources can be accessed without being authenticated. In case the instance requires authentication on all requests the client will receive an **HTTP 401** status code @@ -57,26 +65,38 @@ info: Otherwise unauthenticated clients have all the permissions of the anonymous user. - ### Session-based Authentication + ### Session-based authentication This means you have to login to OpenProject via the Web-Interface to be authenticated in the API. This method is well-suited for clients acting within the browser, like the Angular-Client built into OpenProject. In this case, you always need to pass the HTTP header `X-Requested-With "XMLHttpRequest"` for authentication. - ### API Key through Basic Auth + ### API token as bearer token - Users can authenticate towards the API v3 using basic auth with the user name `apikey` (NOT your login) and the API key as the password. - Users can find their API key on their account page. + Users can authenticate towards the API v3 using an API token as a bearer token. - Example: + For example: ```shell - API_KEY=2519132cdf62dcf5a66fd96394672079f9e9cad1 + API_KEY=opapi-2519132cdf62dcf5a66fd96394672079f9e9cad1 + curl -H "Authorization: Bearer $API_KEY" https://community.openproject.org/api/v3/users/42 + ``` + + Users can generate API tokens on their account page. + + ### API token through Basic Auth + + API tokens can also be used with basic auth, using the user name `apikey` (NOT your login) and the API token as the password. + + For example: + + ```shell + API_KEY=opapi-2519132cdf62dcf5a66fd96394672079f9e9cad1 curl -u apikey:$API_KEY https://community.openproject.org/api/v3/users/42 ``` - ### OAuth2.0 authentication + ### OAuth 2.0 authentication OpenProject allows authentication and authorization with OAuth2 with *Authorization code flow*, as well as *Client credentials* operation modes. @@ -91,7 +111,7 @@ info: - [Client credentials](https://oauth.net/2/grant-types/client-credentials/) - Requires an application to be bound to an impersonating user for non-public access - ### OIDC provider generated JWT as a Bearer token + ### OAuth 2.0 using an external authorization server There is a possibility to use JSON Web Tokens (JWT) generated by an OIDC provider configured in OpenProject as a bearer token to do authenticated requests against the API. The following requirements must be met: @@ -103,7 +123,7 @@ info: - JWT **scope** claim must include a valid scope to access the desired API (e.g. `api_v3` for APIv3) - JWT must be actual (neither expired or too early to be used) - JWT must be passed in Authorization header like: `Authorization: Bearer {jwt}` - - User from **sub** claim must be logged in OpenProject before otherwise it will be not authenticated + - User from **sub** claim must be linked to OpenProject before (e.g. by logging in), otherwise it will be not authenticated In more general terms, OpenProject should be compliant to [RFC 9068](https://www.rfc-editor.org/rfc/rfc9068) when validating access tokens. diff --git a/docs/release-notes/14/14-4-0/README.md b/docs/release-notes/14/14-4-0/README.md index 97690decef1..8ab1ce4d934 100644 --- a/docs/release-notes/14/14-4-0/README.md +++ b/docs/release-notes/14/14-4-0/README.md @@ -10,7 +10,7 @@ release_date: 2024-08-14 Release date: 2024-08-14 -We released [OpenProject 14.4.0](https://community.openproject.org/versions/2063). The release contains several bug fixes and we recommend updating to the newest version. +We released [OpenProject 14.4.0](https://community.openproject.org/versions/2063). The release contains several bug fixes and we recommend updating to the newest version. In these Release Notes, we will give an overview of important technical updates as well as important feature changes. At the end, you will find a complete list of all changes and bug fixes. @@ -22,7 +22,7 @@ OpenProject 14.4 introduces a new feature that allows OpenID clients, such as Ne With this feature, the OpenProject API will validate access tokens issued by the OpenID provider (Keycloak) by checking the token's signature and authenticating the user using the sub claim value. This integration ensures secure and efficient API authentication for OpenID clients. -For more details, take a look at our [API documentation](../../../api/introduction/#oidc-provider-generated-jwt-as-a-bearer-token). +For more details, take a look at our [API documentation](../../../api/introduction/#oauth-20-using-an-external-authorization-server). ### Improve error messages and logs of automatically managed project folders synchronization services/jobs @@ -38,7 +38,7 @@ For more details, see this [work package](https://community.openproject.org/wp/5 ### Personal settings: Dark mode -Dark mode for OpenProject is finally here! In the '[My account](../../../user-guide/account-settings/#look-and-feel)' section under 'Interface', there is an **option labeled 'Mode' where users can now select 'Dark (Beta).'** – as an alternative to the light mode. When the dark mode is selected, the change applies only to that user, not to the entire instance. +Dark mode for OpenProject is finally here! In the '[My account](../../../user-guide/account-settings/#look-and-feel)' section under 'Interface', there is an **option labeled 'Mode' where users can now select 'Dark (Beta).'** – as an alternative to the light mode. When the dark mode is selected, the change applies only to that user, not to the entire instance. ![News setting for dark mode in OpenProject, displayed in dark mode](openproject-14-4-dark-mode.png) @@ -222,12 +222,12 @@ Clicking on the "Details" link will take the user to the diff view, which is als ## Contributions -A very special thank you goes to the City of Cologne again for sponsoring features on project attributes and project lists. +A very special thank you goes to the City of Cologne again for sponsoring features on project attributes and project lists. Also a big thanks to our Community members for reporting bugs and helping us identify and provide fixes. Special thanks for reporting and finding bugs go to Johan Bouduin, Sven Kunze and Marcel Carvalho. -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 the three following users: +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 the three following users: - [Jeff Li](https://crowdin.com/profile/jeff_li) for translations to Chinese Simplified, - [Adam Siemienski](https://crowdin.com/profile/siemienas) for translations to Polish, diff --git a/lib_static/open_project/authentication/strategies/warden/user_api_token.rb b/lib_static/open_project/authentication/strategies/warden/user_api_token.rb new file mode 100644 index 00000000000..ccfabca77bb --- /dev/null +++ b/lib_static/open_project/authentication/strategies/warden/user_api_token.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module OpenProject + module Authentication + module Strategies + module Warden + ## + # Allows users to authenticate using their API key as a Bearer token. + # Note that in order for a user to be able to generate one + # `Setting.rest_api_enabled` has to be `1`. + class UserAPIToken < ::Warden::Strategies::Base + include FailWithHeader + + def valid? + return false unless Setting.rest_api_enabled? + + @access_token = ::Doorkeeper::OAuth::Token.from_bearer_authorization( + ::Doorkeeper::Grape::AuthorizationDecorator.new(request) + ) + return false if @access_token.blank? + + @access_token.start_with?(::Token::API.prefix) + end + + def authenticate! + token = ::Token::API.find_by_plaintext_value(@access_token) # rubocop:disable Rails/DynamicFindBy + return fail_with_header!(error: "invalid_token") if token.nil? + + authentication_result(token.user) + end + + private + + def authentication_result(user) + if user.nil? + return fail_with_header!( + error: "invalid_token", + error_description: "The user identified by the token is not known" + ) + end + + if user.active? + success!(user) + else + fail_with_header!( + error: "invalid_token", + error_description: "The user account is locked" + ) + end + end + end + end + end + end +end diff --git a/spec/models/token/base_token_spec.rb b/spec/models/token/base_token_spec.rb index 00fce765734..42b49041a4e 100644 --- a/spec/models/token/base_token_spec.rb +++ b/spec/models/token/base_token_spec.rb @@ -47,4 +47,18 @@ RSpec.describe Token::Base do expect(described_class.exists?(subject.id)).to be false expect(described_class.exists?(t2.id)).to be true end + + context "when defining a prefix" do + subject { subclass.new(user:) } + + let(:subclass) { Class.new(described_class) { prefix :test } } + + it "has a plaintext value starting with the prefix" do + expect(subject.value).to start_with("test-") + end + + it "has the regular token value after the prefix" do + expect(subject.value.delete_prefix("test-").length).to eq(64) + end + end end diff --git a/spec/requests/api/v3/authentication_spec.rb b/spec/requests/api/v3/authentication_spec.rb index 3768c662e0f..27229bcc0fb 100644 --- a/spec/requests/api/v3/authentication_spec.rb +++ b/spec/requests/api/v3/authentication_spec.rb @@ -225,6 +225,74 @@ RSpec.describe "API V3 Authentication" do end end + describe "API Key as Bearer token" do + let(:token) { create(:api_token, user:) } + let(:bearer_token) { token.plain_value } + let(:expected_message) { "You did not provide the correct credentials." } + + before do + user + + header "Authorization", "Bearer #{bearer_token}" + + get resource + end + + context "with a valid access token" do + it "authenticates successfully" do + expect(last_response).to have_http_status :ok + end + end + + context "with an invalid access token" do + let(:bearer_token) { "opapi-1337" } + let(:expected_www_auth_header) do + %{Bearer realm="OpenProject API", #{resource_metadata}, scope="api_v3", error="invalid_token"} + end + + it "returns unauthorized" do + expect(last_response).to have_http_status :unauthorized + expect(last_response.header["WWW-Authenticate"]).to eq(expected_www_auth_header) + expect(JSON.parse(last_response.body)).to eq(error_response_body) + end + end + + context "when the token's user can't be found" do + let(:expected_www_auth_header) do + %{Bearer realm="OpenProject API", #{resource_metadata}, scope="api_v3", error="invalid_token"} + end + + around do |ex| + # create the token before deleting the user; right now it especially works, because a foreign key constraint prevents + # tokens without users + token + user.destroy! + ex.run + end + + it "returns unauthorized" do + expect(last_response).to have_http_status :unauthorized + expect(last_response.header["WWW-Authenticate"]).to eq(expected_www_auth_header) + expect(JSON.parse(last_response.body)).to eq(error_response_body) + end + end + + context "when the token's user is locked" do + let(:user) { create(:user, :locked) } + let(:expected_www_auth_header) do + "Bearer realm=\"OpenProject API\", #{resource_metadata}, scope=\"api_v3\", error=\"invalid_token\", " \ + "error_description=\"#{expected_error_description}\"" + end + let(:expected_error_description) { "The user account is locked" } + + it "returns unauthorized" do + expect(last_response).to have_http_status :unauthorized + expect(last_response.header["WWW-Authenticate"]).to eq(expected_www_auth_header) + expect(JSON.parse(last_response.body)).to eq(error_response_body) + end + end + end + describe "basic auth" do let(:expected_message) { "You need to be authenticated to access this resource." } diff --git a/spec/requests/mcp/tools_list_spec.rb b/spec/requests/mcp/tools_list_spec.rb index 3440f922f8d..c36870acd92 100644 --- a/spec/requests/mcp/tools_list_spec.rb +++ b/spec/requests/mcp/tools_list_spec.rb @@ -81,9 +81,9 @@ RSpec.describe "MCP tools/list", with_flag: { mcp_server: true } do it_behaves_like "MCP unauthenticated response" end - context "when passing an API key via Basic auth" do + context "when passing an API token via Bearer authentication" do subject do - header "Authorization", "Basic #{Base64.encode64("apikey:#{apikey.plain_value}")}" + header "Authorization", "Bearer #{apikey.plain_value}" header "Content-Type", "application/json" post "/mcp", request_body.to_json end From 2ceacbf7d56d0e26938127c91966483e4d86ebed Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Thu, 5 Feb 2026 06:51:30 -0300 Subject: [PATCH 096/293] Revert "Move 'Open details view' button out of story menu" This reverts commit b1c67cc9b761ed99e9ba7993db3c7ba4885167bc. --- .../assets/sass/backlogs/_master_backlog.sass | 7 ++----- .../components/backlogs/story_component.html.erb | 16 ---------------- .../app/components/backlogs/story_component.rb | 3 +-- .../backlogs/story_menu_component.html.erb | 9 +++++++++ .../components/backlogs/story_component_spec.rb | 7 ------- .../backlogs/story_menu_component_spec.rb | 7 +++++++ 6 files changed, 19 insertions(+), 30 deletions(-) diff --git a/frontend/src/assets/sass/backlogs/_master_backlog.sass b/frontend/src/assets/sass/backlogs/_master_backlog.sass index 919ccd75c09..c05c7a83ba5 100644 --- a/frontend/src/assets/sass/backlogs/_master_backlog.sass +++ b/frontend/src/assets/sass/backlogs/_master_backlog.sass @@ -41,7 +41,7 @@ $op-backlogs-header--points-min-width: 5rem margin-left: var(--stack-gap-normal) .op-backlogs-header--menu - margin-left: calc(var(--stack-gap-normal) * 2 + var(--base-size-32)) + margin-left: var(--stack-gap-normal) .op-backlogs-collapsible display: flex @@ -82,7 +82,7 @@ $op-backlogs-header--points-min-width: 5rem display: grid grid-template-columns: var(--control-xsmall-size) 1fr minmax($op-backlogs-header--points-min-width, max-content) auto grid-template-rows: auto auto - grid-template-areas: "drag_handle info_line points show_button menu" "drag_handle subject subject subject subject" + grid-template-areas: "drag_handle info_line points menu" "drag_handle subject subject subject" align-items: center margin-bottom: var(--base-size-4) @@ -101,9 +101,6 @@ $op-backlogs-header--points-min-width: 5rem .op-backlogs-story--points margin-left: var(--stack-gap-normal) -.op-backlogs-story--show_button - margin-left: var(--stack-gap-normal) - .op-backlogs-story--menu margin-left: var(--stack-gap-normal) diff --git a/modules/backlogs/app/components/backlogs/story_component.html.erb b/modules/backlogs/app/components/backlogs/story_component.html.erb index 43d9477cf84..a71589f147b 100644 --- a/modules/backlogs/app/components/backlogs/story_component.html.erb +++ b/modules/backlogs/app/components/backlogs/story_component.html.erb @@ -53,22 +53,6 @@ See COPYRIGHT and LICENSE files for more details. <% end %> <% end %> - <% grid.with_area(:show_button) do %> - <%= - render( - Primer::Beta::IconButton.new( - tag: :a, - scheme: :invisible, - icon: :"op-view-split", - "aria-label": t(:"js.button_open_details"), - href: details_backlogs_project_backlogs_path(project, story), - data: { turbo_frame: "content-bodyRight", turbo_action: "advance" }, - tooltip_direction: :se - ) - ) - %> - <% end %> - <% grid.with_area(:menu) do %> <%= render(Backlogs::StoryMenuComponent.new(story:, sprint:, max_position:)) %> <% end %> diff --git a/modules/backlogs/app/components/backlogs/story_component.rb b/modules/backlogs/app/components/backlogs/story_component.rb index c9fb2e4311d..39c37f48a7f 100644 --- a/modules/backlogs/app/components/backlogs/story_component.rb +++ b/modules/backlogs/app/components/backlogs/story_component.rb @@ -32,14 +32,13 @@ module Backlogs class StoryComponent < ApplicationComponent include OpPrimer::ComponentHelpers - attr_reader :story, :sprint, :project, :max_position, :current_user + attr_reader :story, :sprint, :max_position, :current_user def initialize(story:, sprint:, max_position:, current_user: User.current) super() @story = story @sprint = sprint - @project = sprint.project @max_position = max_position @current_user = current_user end diff --git a/modules/backlogs/app/components/backlogs/story_menu_component.html.erb b/modules/backlogs/app/components/backlogs/story_menu_component.html.erb index cfc4b4eb40e..424bb34efbd 100644 --- a/modules/backlogs/app/components/backlogs/story_menu_component.html.erb +++ b/modules/backlogs/app/components/backlogs/story_menu_component.html.erb @@ -36,6 +36,15 @@ See COPYRIGHT and LICENSE files for more details. tooltip_direction: :se ) + menu.with_item( + tag: :a, + label: t(:"js.button_open_details"), + href: details_backlogs_project_backlogs_path(project, story), + content_arguments: { turbo_frame: "content-bodyRight", turbo_action: "advance" } + ) do |item| + item.with_leading_visual_icon(icon: :"op-view-split") + end + menu.with_item( tag: :a, label: t(:"js.button_open_fullscreen"), diff --git a/modules/backlogs/spec/components/backlogs/story_component_spec.rb b/modules/backlogs/spec/components/backlogs/story_component_spec.rb index ef289ebc9db..e09562c2701 100644 --- a/modules/backlogs/spec/components/backlogs/story_component_spec.rb +++ b/modules/backlogs/spec/components/backlogs/story_component_spec.rb @@ -92,13 +92,6 @@ RSpec.describe Backlogs::StoryComponent, type: :component do expect(page).to have_text("5 points") end - it "shows Open details link (split view)" do - render_component - - expect(page).to have_text(I18n.t(:"js.button_open_details")) - expect(page).to have_octicon(:"op-view-split") - end - it "renders StoryMenuComponent" do render_component diff --git a/modules/backlogs/spec/components/backlogs/story_menu_component_spec.rb b/modules/backlogs/spec/components/backlogs/story_menu_component_spec.rb index ecd72ae492e..99758863c03 100644 --- a/modules/backlogs/spec/components/backlogs/story_menu_component_spec.rb +++ b/modules/backlogs/spec/components/backlogs/story_menu_component_spec.rb @@ -66,6 +66,13 @@ RSpec.describe Backlogs::StoryMenuComponent, type: :component do end describe "standard items" do + it "shows Open details link (split view)" do + render_component + + expect(page).to have_text(I18n.t(:"js.button_open_details")) + expect(page).to have_octicon(:"op-view-split") + end + it "shows Open fullscreen link (full page)" do render_component From a90207ca4ff3fe63c8e51795b6e15550cea31acd Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Thu, 5 Feb 2026 07:04:32 -0300 Subject: [PATCH 097/293] Vertically center align story top row --- frontend/src/assets/sass/backlogs/_master_backlog.sass | 8 +------- .../app/components/backlogs/story_component.html.erb | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/frontend/src/assets/sass/backlogs/_master_backlog.sass b/frontend/src/assets/sass/backlogs/_master_backlog.sass index c05c7a83ba5..f18df999ef3 100644 --- a/frontend/src/assets/sass/backlogs/_master_backlog.sass +++ b/frontend/src/assets/sass/backlogs/_master_backlog.sass @@ -82,22 +82,16 @@ $op-backlogs-header--points-min-width: 5rem display: grid grid-template-columns: var(--control-xsmall-size) 1fr minmax($op-backlogs-header--points-min-width, max-content) auto grid-template-rows: auto auto - grid-template-areas: "drag_handle info_line points menu" "drag_handle subject subject subject" + grid-template-areas: "drag_handle info_line points menu" ". subject subject subject" align-items: center margin-bottom: var(--base-size-4) .op-backlogs-story--drag_handle - align-self: start display: flex - padding-top: var(--base-size-8) .op-backlogs-story--drag_handle_button padding: var(--base-size-4) -.op-backlogs-story--info_line - align-self: end - margin-bottom: var(--base-size-4) - .op-backlogs-story--points margin-left: var(--stack-gap-normal) diff --git a/modules/backlogs/app/components/backlogs/story_component.html.erb b/modules/backlogs/app/components/backlogs/story_component.html.erb index a71589f147b..aff0f073f4c 100644 --- a/modules/backlogs/app/components/backlogs/story_component.html.erb +++ b/modules/backlogs/app/components/backlogs/story_component.html.erb @@ -48,7 +48,7 @@ See COPYRIGHT and LICENSE files for more details. <% end %> <% grid.with_area(:points) do %> - <%= render(Primer::Beta::Truncate.new(color: :subtle, mt: 1)) do %> + <%= render(Primer::Beta::Truncate.new(color: :subtle)) do %> <%= t(:"backlogs.points", count: story_points) %> <% end %> <% end %> From 61ff200bdc74e3e7d458abd379fcd4afe9be12f3 Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Thu, 5 Feb 2026 12:19:48 +0100 Subject: [PATCH 098/293] Add container query for better page layout responsiveness --- .../assets/sass/backlogs/_master_backlog.sass | 23 ++++++++++++++++++- .../views/rb_master_backlogs/_list.html.erb | 8 +++---- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/frontend/src/assets/sass/backlogs/_master_backlog.sass b/frontend/src/assets/sass/backlogs/_master_backlog.sass index f18df999ef3..ac2d8072d2e 100644 --- a/frontend/src/assets/sass/backlogs/_master_backlog.sass +++ b/frontend/src/assets/sass/backlogs/_master_backlog.sass @@ -45,7 +45,7 @@ $op-backlogs-header--points-min-width: 5rem .op-backlogs-collapsible display: flex - flex-wrap: flex + flex-wrap: wrap align-items: center column-gap: var(--stack-gap-normal) row-gap: var(--base-size-4) @@ -112,3 +112,24 @@ $op-backlogs-header--points-min-width: 5rem & > :first-child flex: 1 1 auto min-width: 33% + +.op-backlogs-page + display: block + container-name: backlogsListsContainer + container-type: inline-size + +.op-backlogs-container + display: flex + flex-direction: row + gap: var(--stack-gap-normal) + +.op-backlogs-lists + display: flex + flex-direction: column + gap: var(--stack-gap-normal) + flex: 1 1 100% + overflow: hidden + +@container backlogsListsContainer (max-width: 543px) + .op-backlogs-container + flex-direction: column diff --git a/modules/backlogs/app/views/rb_master_backlogs/_list.html.erb b/modules/backlogs/app/views/rb_master_backlogs/_list.html.erb index 376cb8f1fa9..4295b3a50ac 100644 --- a/modules/backlogs/app/views/rb_master_backlogs/_list.html.erb +++ b/modules/backlogs/app/views/rb_master_backlogs/_list.html.erb @@ -1,4 +1,4 @@ - + <% if @owner_backlogs.empty? && @sprint_backlogs.empty? %> <%= render(Primer::Beta::Blankslate.new(border: true, spacious: true)) do |blankslate| @@ -11,11 +11,11 @@ end %> <% else %> -
    -
    +
    +
    <%= render(Backlogs::BacklogComponent.with_collection(@sprint_backlogs, project: @project)) %>
    -
    +
    <%= render(Backlogs::BacklogComponent.with_collection(@owner_backlogs, project: @project)) %>
    From 2376635730aa4e3b727a974cc282a6a1427e576e Mon Sep 17 00:00:00 2001 From: Ivan Kuchin Date: Wed, 4 Feb 2026 17:00:19 +0100 Subject: [PATCH 099/293] [#71375] Refactoring while adding custom field comments https://community.openproject.org/work_packages/71375 From bc0d5c5443abd6a452dbbf25969303561bee3731 Mon Sep 17 00:00:00 2001 From: Ivan Kuchin Date: Thu, 15 Jan 2026 17:33:35 +0100 Subject: [PATCH 100/293] rename ProjectCustomFieldEditController to ProjectCustomFieldModalController It is going to allow also to view comment for users with view, but no edit permission --- ...ts => project-custom-field-modal.controller.ts} | 2 +- .../project_custom_fields/item_component.rb | 14 +++++++------- spec/features/projects/copy_spec.rb | 2 +- .../overview_page/dialog/permission_spec.rb | 6 +++--- spec/support/pages/projects/show.rb | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) rename frontend/src/stimulus/controllers/dynamic/{project-custom-field-edit.controller.ts => project-custom-field-modal.controller.ts} (96%) diff --git a/frontend/src/stimulus/controllers/dynamic/project-custom-field-edit.controller.ts b/frontend/src/stimulus/controllers/dynamic/project-custom-field-modal.controller.ts similarity index 96% rename from frontend/src/stimulus/controllers/dynamic/project-custom-field-edit.controller.ts rename to frontend/src/stimulus/controllers/dynamic/project-custom-field-modal.controller.ts index d5b434c910f..140f6e78824 100644 --- a/frontend/src/stimulus/controllers/dynamic/project-custom-field-edit.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/project-custom-field-modal.controller.ts @@ -31,7 +31,7 @@ import { Controller } from '@hotwired/stimulus'; -export default class ProjectCustomFieldEditController extends Controller { +export default class ProjectCustomFieldModalController extends Controller { static values = { url: { type: String }, }; diff --git a/modules/overviews/app/components/overviews/project_custom_fields/item_component.rb b/modules/overviews/app/components/overviews/project_custom_fields/item_component.rb index 2779b3c26b3..0eef8f53276 100644 --- a/modules/overviews/app/components/overviews/project_custom_fields/item_component.rb +++ b/modules/overviews/app/components/overviews/project_custom_fields/item_component.rb @@ -65,13 +65,13 @@ module Overviews tag: :div, classes: "project-custom-field-clickable", data: { - controller: "project-custom-field-edit async-dialog", - "project-custom-field-edit-url-value": edit_project_custom_field_path(project_id: @project.id, + controller: "project-custom-field-modal async-dialog", + "project-custom-field-modal-url-value": edit_project_custom_field_path(project_id: @project.id, id: @project_custom_field.id), - action: "click->project-custom-field-edit#openEditDialog " \ - "keydown.enter->project-custom-field-edit#openEditDialog " \ - "keydown.space->project-custom-field-edit#openEditDialog " \ - "project-custom-field-edit:open-dialog->async-dialog#handleOpenDialog" + action: "click->project-custom-field-modal#openEditDialog " \ + "keydown.enter->project-custom-field-modal#openEditDialog " \ + "keydown.space->project-custom-field-modal#openEditDialog " \ + "project-custom-field-modal:open-dialog->async-dialog#handleOpenDialog" }, aria: { label: [ @@ -81,7 +81,7 @@ module Overviews }, role: "button", tabindex: 0, - test_selector: "project-custom-field-edit-button-#{@project_custom_field.id}" + test_selector: "project-custom-field-modal-button-#{@project_custom_field.id}" ) end diff --git a/spec/features/projects/copy_spec.rb b/spec/features/projects/copy_spec.rb index 21f7d067321..e2c5b453c18 100644 --- a/spec/features/projects/copy_spec.rb +++ b/spec/features/projects/copy_spec.rb @@ -470,7 +470,7 @@ RSpec.describe "Projects copy", :js, overview_page.within_project_attributes_sidebar do # User has no permission to edit project attributes. - expect(page).to have_no_css("[data-test-selector*='project-custom-field-edit-button']") + expect(page).to have_no_css("[data-test-selector*='project-custom-field-modal-button-']") # The custom fields are still copied from the parent project. expect(page).to have_content(project_custom_field.name) expect(page).to have_content("some text cf") diff --git a/spec/features/projects/project_custom_fields/overview_page/dialog/permission_spec.rb b/spec/features/projects/project_custom_fields/overview_page/dialog/permission_spec.rb index b03e99005eb..8e4ae7240b7 100644 --- a/spec/features/projects/project_custom_fields/overview_page/dialog/permission_spec.rb +++ b/spec/features/projects/project_custom_fields/overview_page/dialog/permission_spec.rb @@ -67,7 +67,7 @@ RSpec.describe "Edit project custom fields on project overview page", :js do it "does not show the edit buttons" do overview_page.within_project_attributes_sidebar do - expect(page).to have_no_test_selector("[data-test-selector*='project-custom-field-edit-button']") + expect(page).to have_no_test_selector("[data-test-selector*='project-custom-field-modal-button-']") end end end @@ -83,7 +83,7 @@ RSpec.describe "Edit project custom fields on project overview page", :js do it "does not show the edit buttons" do overview_page.within_project_attributes_sidebar do - expect(page).to have_no_css("[data-test-selector*='project-custom-field-edit-button']") + expect(page).to have_no_css("[data-test-selector*='project-custom-field-modal-button-']") end end end @@ -96,7 +96,7 @@ RSpec.describe "Edit project custom fields on project overview page", :js do it "shows the edit buttons" do overview_page.within_project_attributes_sidebar do - expect(page).to have_css("[data-test-selector*='project-custom-field-edit-button']", count: 13) + expect(page).to have_css("[data-test-selector*='project-custom-field-modal-button-']", count: 13) end end end diff --git a/spec/support/pages/projects/show.rb b/spec/support/pages/projects/show.rb index 1556315f5b7..543a97e987a 100644 --- a/spec/support/pages/projects/show.rb +++ b/spec/support/pages/projects/show.rb @@ -93,7 +93,7 @@ module Pages # Once we create the project custom field inline editing, this can be reverted to a normal # capybara click method call. page.execute_script( - "document.querySelector('[data-test-selector=\"project-custom-field-edit-button-#{custom_field.id}\"]').click()" + "document.querySelector('[data-test-selector=\"project-custom-field-modal-button-#{custom_field.id}\"]').click()" ) end From ace74395f37ff90bbf823c45af1b8d60d958cfe9 Mon Sep 17 00:00:00 2001 From: Ivan Kuchin Date: Thu, 15 Jan 2026 20:00:57 +0100 Subject: [PATCH 101/293] rename modal controller method from openEditDialog to open --- .../dynamic/project-custom-field-modal.controller.ts | 2 +- .../overviews/project_custom_fields/item_component.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/stimulus/controllers/dynamic/project-custom-field-modal.controller.ts b/frontend/src/stimulus/controllers/dynamic/project-custom-field-modal.controller.ts index 140f6e78824..018dce97cb8 100644 --- a/frontend/src/stimulus/controllers/dynamic/project-custom-field-modal.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/project-custom-field-modal.controller.ts @@ -38,7 +38,7 @@ export default class ProjectCustomFieldModalController extends Controller { declare urlValue:string; - openEditDialog(event:Event) { + open(event:Event) { const target = event.target as HTMLElement; // Check if the event is on an interactive element that should be ignored diff --git a/modules/overviews/app/components/overviews/project_custom_fields/item_component.rb b/modules/overviews/app/components/overviews/project_custom_fields/item_component.rb index 0eef8f53276..4518c386947 100644 --- a/modules/overviews/app/components/overviews/project_custom_fields/item_component.rb +++ b/modules/overviews/app/components/overviews/project_custom_fields/item_component.rb @@ -68,9 +68,9 @@ module Overviews controller: "project-custom-field-modal async-dialog", "project-custom-field-modal-url-value": edit_project_custom_field_path(project_id: @project.id, id: @project_custom_field.id), - action: "click->project-custom-field-modal#openEditDialog " \ - "keydown.enter->project-custom-field-modal#openEditDialog " \ - "keydown.space->project-custom-field-modal#openEditDialog " \ + action: "click->project-custom-field-modal#open " \ + "keydown.enter->project-custom-field-modal#open " \ + "keydown.space->project-custom-field-modal#open " \ "project-custom-field-modal:open-dialog->async-dialog#handleOpenDialog" }, aria: { From 5672117159c5527e6b93c51f6e799ec9d5d2b31c Mon Sep 17 00:00:00 2001 From: Ivan Kuchin Date: Thu, 15 Jan 2026 21:12:36 +0100 Subject: [PATCH 102/293] remove footer divider from edit dialog --- .../project_custom_fields/edit_dialog_component.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/overviews/app/components/overviews/project_custom_fields/edit_dialog_component.html.erb b/modules/overviews/app/components/overviews/project_custom_fields/edit_dialog_component.html.erb index ccf253a0146..ceefbfcbf86 100644 --- a/modules/overviews/app/components/overviews/project_custom_fields/edit_dialog_component.html.erb +++ b/modules/overviews/app/components/overviews/project_custom_fields/edit_dialog_component.html.erb @@ -11,7 +11,7 @@ d.with_body(classes: "Overlay-body_autocomplete_height") do render(Overviews::ProjectCustomFields::EditComponent.new(project_custom_field: @project_custom_field, project: @project, wrapper_id:)) end - d.with_footer(show_divider: true) do + d.with_footer do component_collection do |footer_collection| footer_collection.with_component( Primer::Beta::Button.new( From 982fbf520adb04845cfa8fb2ceecf3864febc3dd Mon Sep 17 00:00:00 2001 From: Ivan Kuchin Date: Thu, 15 Jan 2026 21:18:02 +0100 Subject: [PATCH 103/293] use custom field section title for dialog title --- config/locales/en.yml | 1 - .../edit_dialog_component.html.erb | 2 +- .../edit_dialog_component.rb | 4 ++++ .../dialog/attribute_help_texts_spec.rb | 20 +++++++++---------- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 9117b6f8d16..bde2158af02 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -3366,7 +3366,6 @@ en: label_duplicate: "duplicate" label_duplicates: "duplicates" label_edit: "Edit" - label_edit_attribute: "Edit attribute" label_edit_x: "Edit: %{x}" label_enable_multi_select: "Toggle multiselect" label_enabled_project_custom_fields: "Enabled custom fields" diff --git a/modules/overviews/app/components/overviews/project_custom_fields/edit_dialog_component.html.erb b/modules/overviews/app/components/overviews/project_custom_fields/edit_dialog_component.html.erb index ceefbfcbf86..7cef6d7b159 100644 --- a/modules/overviews/app/components/overviews/project_custom_fields/edit_dialog_component.html.erb +++ b/modules/overviews/app/components/overviews/project_custom_fields/edit_dialog_component.html.erb @@ -1,7 +1,7 @@ <%= render( Primer::Alpha::Dialog.new( - title: t("label_edit_attribute"), + title: dialog_title, classes: "Overlay--size-large-portrait", size: :large, id: dialog_id diff --git a/modules/overviews/app/components/overviews/project_custom_fields/edit_dialog_component.rb b/modules/overviews/app/components/overviews/project_custom_fields/edit_dialog_component.rb index 8a906b80d8d..54bd8d59a80 100644 --- a/modules/overviews/app/components/overviews/project_custom_fields/edit_dialog_component.rb +++ b/modules/overviews/app/components/overviews/project_custom_fields/edit_dialog_component.rb @@ -43,6 +43,10 @@ module Overviews private + def dialog_title + @project_custom_field.project_custom_field_section.name + end + def dialog_id "edit-project-custom-field-dialog-#{@project_custom_field.id}" end diff --git a/spec/features/projects/project_custom_fields/overview_page/dialog/attribute_help_texts_spec.rb b/spec/features/projects/project_custom_fields/overview_page/dialog/attribute_help_texts_spec.rb index f96fb58d2ce..a8a65487363 100644 --- a/spec/features/projects/project_custom_fields/overview_page/dialog/attribute_help_texts_spec.rb +++ b/spec/features/projects/project_custom_fields/overview_page/dialog/attribute_help_texts_spec.rb @@ -47,7 +47,7 @@ RSpec.describe "Edit project custom fields on project overview page", "attribute it "shows field labels without help text link" do input_fields.each do |custom_field| edit_dialog = overview_page.open_edit_dialog_for_custom_field(custom_field) - edit_dialog.expect_title "Edit attribute" + edit_dialog.expect_title "Input fields" edit_dialog.expect_field_label_without_help_text custom_field.name edit_dialog.close end @@ -66,7 +66,7 @@ RSpec.describe "Edit project custom fields on project overview page", "attribute it "shows field labels with help text link" do input_fields.each do |custom_field| edit_dialog = overview_page.open_edit_dialog_for_custom_field(custom_field) - edit_dialog.expect_title "Edit attribute" + edit_dialog.expect_title "Input fields" edit_dialog.expect_field_label_with_help_text custom_field.name edit_dialog.close end @@ -75,7 +75,7 @@ RSpec.describe "Edit project custom fields on project overview page", "attribute context "without attachments" do it "shows help text modal on clicking help text link" do edit_dialog = overview_page.open_edit_dialog_for_custom_field(date_project_custom_field) - edit_dialog.expect_title "Edit attribute" + edit_dialog.expect_title "Input fields" edit_dialog.click_help_text_link_for_label "Date field" @@ -95,7 +95,7 @@ RSpec.describe "Edit project custom fields on project overview page", "attribute it "shows help text modal, including attachments, on clicking help text link" do edit_dialog = overview_page.open_edit_dialog_for_custom_field(integer_project_custom_field) - edit_dialog.expect_title "Edit attribute" + edit_dialog.expect_title "Input fields" edit_dialog.click_help_text_link_for_label "Integer field" expect(page).to have_modal "Integer field" @@ -129,7 +129,7 @@ RSpec.describe "Edit project custom fields on project overview page", "attribute it "shows field labels without help text link" do select_fields.each do |custom_field| edit_dialog = overview_page.open_edit_dialog_for_custom_field(custom_field) - edit_dialog.expect_title "Edit attribute" + edit_dialog.expect_title "Select fields" edit_dialog.expect_field_label_without_help_text custom_field.name edit_dialog.close end @@ -144,7 +144,7 @@ RSpec.describe "Edit project custom fields on project overview page", "attribute it "shows field labels with help text link" do select_fields.each do |custom_field| edit_dialog = overview_page.open_edit_dialog_for_custom_field(custom_field) - edit_dialog.expect_title "Edit attribute" + edit_dialog.expect_title "Select fields" edit_dialog.expect_field_label_with_help_text custom_field.name edit_dialog.close end @@ -152,7 +152,7 @@ RSpec.describe "Edit project custom fields on project overview page", "attribute it "shows help text modal on clicking help text link" do edit_dialog = overview_page.open_edit_dialog_for_custom_field(user_project_custom_field) - edit_dialog.expect_title "Edit attribute" + edit_dialog.expect_title "Select fields" edit_dialog.click_help_text_link_for_label "User field" @@ -172,7 +172,7 @@ RSpec.describe "Edit project custom fields on project overview page", "attribute it "shows field labels without help text link" do multi_select_fields.each do |custom_field| edit_dialog = overview_page.open_edit_dialog_for_custom_field(custom_field) - edit_dialog.expect_title "Edit attribute" + edit_dialog.expect_title "Multi select fields" edit_dialog.expect_field_label_without_help_text custom_field.name edit_dialog.close end @@ -193,7 +193,7 @@ RSpec.describe "Edit project custom fields on project overview page", "attribute it "shows field labels with help text link" do multi_select_fields.each do |custom_field| edit_dialog = overview_page.open_edit_dialog_for_custom_field(custom_field) - edit_dialog.expect_title "Edit attribute" + edit_dialog.expect_title "Multi select fields" edit_dialog.expect_field_label_with_help_text custom_field.name edit_dialog.close end @@ -201,7 +201,7 @@ RSpec.describe "Edit project custom fields on project overview page", "attribute it "shows help text modal on clicking help text link" do edit_dialog = overview_page.open_edit_dialog_for_custom_field(multi_list_project_custom_field) - edit_dialog.expect_title "Edit attribute" + edit_dialog.expect_title "Multi select fields" edit_dialog.click_help_text_link_for_label "Multi list field" From 7e1716eb23a7bd9e11310dc45430c4a5c7a41d0a Mon Sep 17 00:00:00 2001 From: Ivan Kuchin Date: Fri, 16 Jan 2026 14:34:49 +0100 Subject: [PATCH 104/293] compare customized not only by id in CustomField#first_calculation_error This is done mostly for simplification, also it could be a problem: CustomField is subclassed, so for example first_calculation_error on ProjectCustomField instance should be used with Project instance, but using for example instance of WorkPackage if comparing only by id would possibly lead to an unexpected result --- app/models/custom_field.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/custom_field.rb b/app/models/custom_field.rb index 37c66278308..56be3f48e53 100644 --- a/app/models/custom_field.rb +++ b/app/models/custom_field.rb @@ -369,7 +369,7 @@ class CustomField < ApplicationRecord # Use a ruby finder to avoid hitting the database with N+1 queries on the project list page, # the errors are eager loaded via the Queries::Projects::CustomFieldContext. - calculated_value_errors.find { it.customized_id == customized.id } + calculated_value_errors.find { it.customized == customized } end private From f91bee9233728ef18076832c263cc503e237daee Mon Sep 17 00:00:00 2001 From: Ivan Kuchin Date: Mon, 19 Jan 2026 18:51:26 +0100 Subject: [PATCH 105/293] rename confusingly named user_can_view_project? to user_can_view_project_attributes? --- app/components/projects/row_component.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/components/projects/row_component.rb b/app/components/projects/row_component.rb index 555b6152a09..1d9a04f0344 100644 --- a/app/components/projects/row_component.rb +++ b/app/components/projects/row_component.rb @@ -83,7 +83,7 @@ module Projects end def custom_field_column(column) # rubocop:disable Metrics/AbcSize - return nil unless user_can_view_project? + return nil unless user_can_view_project_attributes? cf = column.custom_field custom_value = project.formatted_custom_value_for(cf) @@ -196,7 +196,7 @@ module Projects end def project_status - return nil unless user_can_view_project? + return nil unless user_can_view_project_attributes? content = "".html_safe @@ -212,7 +212,7 @@ module Projects end def status_explanation - return nil unless user_can_view_project? + return nil unless user_can_view_project_attributes? if project.status_explanation.present? && project.status_explanation render OpenProject::Common::AttributeComponent.new("dialog-#{project.id}-status-explanation", @@ -222,7 +222,7 @@ module Projects end def description - return nil unless user_can_view_project? + return nil unless user_can_view_project_attributes? if project.description.present? render OpenProject::Common::AttributeComponent.new("dialog-#{project.id}-description", @@ -436,7 +436,7 @@ module Projects end end - def user_can_view_project? + def user_can_view_project_attributes? User.current.allowed_in_project?(:view_project_attributes, project) end From 43a1df6684e247766b76c08a74c3366a23c7d5c7 Mon Sep 17 00:00:00 2001 From: Ivan Kuchin Date: Mon, 19 Jan 2026 19:46:07 +0100 Subject: [PATCH 106/293] replace formatted argument with format for attribute component It was unclear whether formatted means already formatted or that it should be, hopefully naming it as an action will be clearer --- app/components/open_project/common/attribute_component.rb | 8 ++++---- app/components/projects/row_component.rb | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/components/open_project/common/attribute_component.rb b/app/components/open_project/common/attribute_component.rb index 4bb31884475..5ebbdb8a56d 100644 --- a/app/components/open_project/common/attribute_component.rb +++ b/app/components/open_project/common/attribute_component.rb @@ -37,11 +37,11 @@ module OpenProject :description, :lines, :background_reference_id, - :formatted + :format PARAGRAPH_CSS_CLASS = "op-uc-p" - def initialize(id, name, description, lines: 1, background_reference_id: "content", formatted: false, **args) + def initialize(id, name, description, lines: 1, background_reference_id: "content", format: true, **args) super() @id = id @name = name @@ -49,7 +49,7 @@ module OpenProject @system_arguments = args @lines = lines @background_reference_id = background_reference_id - @formatted = formatted + @format = format end def short_text @@ -61,7 +61,7 @@ module OpenProject end def full_text - @full_text ||= formatted ? description : helpers.format_text(description) + @full_text ||= format ? helpers.format_text(description) : description end def display_expand_button_value diff --git a/app/components/projects/row_component.rb b/app/components/projects/row_component.rb index 1d9a04f0344..6c783553b23 100644 --- a/app/components/projects/row_component.rb +++ b/app/components/projects/row_component.rb @@ -93,7 +93,7 @@ module Projects "dialog-#{project.id}-cf-#{cf.id}", cf.name, custom_value, - formatted: true + format: false # already formatted ) elsif custom_value.is_a?(Array) safe_join(Array(custom_value).compact_blank, ", ") From ccc9e1225eb2ccf88c9d8618e92a6c513b5c0191 Mon Sep 17 00:00:00 2001 From: Ivan Kuchin Date: Mon, 19 Jan 2026 20:38:26 +0100 Subject: [PATCH 107/293] bits of code cleanup in custom field export formatter --- app/models/exports/formatters/custom_field.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/exports/formatters/custom_field.rb b/app/models/exports/formatters/custom_field.rb index 22a166c89c1..a4e236e93e3 100644 --- a/app/models/exports/formatters/custom_field.rb +++ b/app/models/exports/formatters/custom_field.rb @@ -39,7 +39,7 @@ module Exports # Takes a WorkPackage or Project and an attribute and returns the value to be exported. def retrieve_value(object) custom_field = find_custom_field(object) - return "" if custom_field.nil? + return nil if custom_field.nil? format_for_export(object, custom_field) end @@ -68,8 +68,8 @@ module Exports ## # Finds a custom field from the attribute identifier def find_custom_field(object) - id = attribute.to_s.sub("cf_", "").to_i - object.available_custom_fields.detect { |cf| cf.id == id } + id = attribute.to_s.delete_prefix("cf_").to_i + object.available_custom_fields.find { it.id == id } end end end From 9b7df9aef3584a77ad13c54b3e45d0509e68ca1b Mon Sep 17 00:00:00 2001 From: Ivan Kuchin Date: Wed, 21 Jan 2026 20:54:33 +0100 Subject: [PATCH 108/293] add custom_field_class method for customized models --- .../acts_as_customizable/lib/acts_as_customizable.rb | 6 ++++++ spec/support/shared/acts_as_customizable.rb | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb b/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb index ffa96df9347..878836074ba 100644 --- a/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb +++ b/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb @@ -478,6 +478,12 @@ module Redmine end module AddClassMethods + def custom_field_class + "#{name}CustomField".constantize + rescue NameError + nil + end + def available_custom_fields(_model) RequestStore.fetch(:"#{name.underscore}_custom_fields") do CustomField.where(type: "#{name}CustomField").order(:position) diff --git a/spec/support/shared/acts_as_customizable.rb b/spec/support/shared/acts_as_customizable.rb index def2f2f92f3..fb1834d2c93 100644 --- a/spec/support/shared/acts_as_customizable.rb +++ b/spec/support/shared/acts_as_customizable.rb @@ -29,6 +29,13 @@ #++ RSpec.shared_examples_for "acts_as_customizable included" do + describe ".custom_field_class" do + it "returns the corresponding CustomField subclass" do + expect(described_class.custom_field_class) + .to eq("#{described_class.name}CustomField".constantize) + end + end + describe "#custom_field_changes" do context "when no custom field value exists" do before do From 9f6a68f29fcc8e98bc375b315e8ed903ed6e0fd0 Mon Sep 17 00:00:00 2001 From: Ivan Kuchin Date: Wed, 21 Jan 2026 20:58:24 +0100 Subject: [PATCH 109/293] rescue only NameError in CustomField.customized_class --- app/models/custom_field.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/custom_field.rb b/app/models/custom_field.rb index 56be3f48e53..55b4d5b62e7 100644 --- a/app/models/custom_field.rb +++ b/app/models/custom_field.rb @@ -258,7 +258,7 @@ class CustomField < ApplicationRecord name =~ /\A(.+)CustomField\z/ begin $1.constantize - rescue StandardError + rescue NameError nil end end From 39b733fdff2fc4cd221d36ac9bdb080dca454ec8 Mon Sep 17 00:00:00 2001 From: Ivan Kuchin Date: Mon, 26 Jan 2026 21:25:50 +0100 Subject: [PATCH 110/293] to_h not needed after index_with and index_value also accepts value instead of block --- spec/models/permitted_params_spec.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/spec/models/permitted_params_spec.rb b/spec/models/permitted_params_spec.rb index 0bc7ea4fe01..b658299b08c 100644 --- a/spec/models/permitted_params_spec.rb +++ b/spec/models/permitted_params_spec.rb @@ -102,7 +102,7 @@ RSpec.describe PermittedParams do acceptable_params = %w(time_zone comments_sorting warn_on_leaving_unsaved) - acceptable_params.index_with { |_x| "value" } + acceptable_params.index_with("value") end it_behaves_like "allows params" @@ -111,7 +111,7 @@ RSpec.describe PermittedParams do describe "#news" do let(:attribute) { :news } let(:hash) do - %w(title summary description).index_with { |_x| "value" }.to_h + %w(title summary description).index_with("value") end it_behaves_like "allows params" @@ -120,7 +120,7 @@ RSpec.describe PermittedParams do describe "#comment" do let(:attribute) { :comment } let(:hash) do - %w(commented author comments).index_with { |_x| "value" }.to_h + %w(commented author comments).index_with("value") end it_behaves_like "allows params" @@ -129,7 +129,7 @@ RSpec.describe PermittedParams do describe "#watcher" do let(:attribute) { :watcher } let(:hash) do - %w(watchable user user_id).index_with { |_x| "value" }.to_h + %w(watchable user user_id).index_with("value") end it_behaves_like "allows params" @@ -138,7 +138,7 @@ RSpec.describe PermittedParams do describe "#reply" do let(:attribute) { :reply } let(:hash) do - %w(content subject).index_with { |_x| "value" }.to_h + %w(content subject).index_with("value") end it_behaves_like "allows params" @@ -147,7 +147,7 @@ RSpec.describe PermittedParams do describe "#wiki" do let(:attribute) { :wiki } let(:hash) do - %w(start_page).index_with { |_x| "value" }.to_h + %w(start_page).index_with("value") end it_behaves_like "allows params" @@ -165,7 +165,7 @@ RSpec.describe PermittedParams do describe "#category" do let(:attribute) { :category } let(:hash) do - %w(name assigned_to_id).index_with { |_x| "value" }.to_h + %w(name assigned_to_id).index_with("value") end it_behaves_like "allows params" @@ -177,7 +177,7 @@ RSpec.describe PermittedParams do context "with whitelisted params" do let(:hash) do %w(name description effective_date due_date - start_date wiki_page_title status sharing).index_with { |_x| "value" }.to_h + start_date wiki_page_title status sharing).index_with("value") end it_behaves_like "allows params" @@ -201,7 +201,7 @@ RSpec.describe PermittedParams do context "with no instance passed" do let(:expected_allowed_params) do - %w(subject content forum_id).index_with { |_x| "value" }.to_h + %w(subject content forum_id).index_with("value") end let(:hash) do From 8854a3b406f039288a99bee563e4378f5d12fe17 Mon Sep 17 00:00:00 2001 From: Ivan Kuchin Date: Mon, 26 Jan 2026 21:33:43 +0100 Subject: [PATCH 111/293] rename expected_allowed_params to expected_permitted --- spec/models/permitted_params_spec.rb | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/spec/models/permitted_params_spec.rb b/spec/models/permitted_params_spec.rb index b658299b08c..087d608dd28 100644 --- a/spec/models/permitted_params_spec.rb +++ b/spec/models/permitted_params_spec.rb @@ -61,7 +61,7 @@ RSpec.describe PermittedParams do include_context "with prepare params comparison" it do - expected = defined?(expected_allowed_params) ? expected_allowed_params : hash + expected = defined?(expected_permitted) ? expected_permitted : hash expect(subject).to eq(expected) end end @@ -200,12 +200,12 @@ RSpec.describe PermittedParams do let(:attribute) { :message } context "with no instance passed" do - let(:expected_allowed_params) do + let(:expected_permitted) do %w(subject content forum_id).index_with("value") end let(:hash) do - expected_allowed_params.merge(evil: "true", sticky: "true", locked: "true") + expected_permitted.merge(evil: "true", sticky: "true", locked: "true") end it_behaves_like "allows params" @@ -219,7 +219,7 @@ RSpec.describe PermittedParams do context "with project instance passed" do let(:project) { instance_double(Project) } - let(:expected_allowed_params) do + let(:expected_permitted) do { "subject" => "value", "content" => "value", "forum_id" => "value", @@ -228,7 +228,7 @@ RSpec.describe PermittedParams do end let(:hash) do - ActionController::Parameters.new("message" => expected_allowed_params.merge(evil: "true")) + ActionController::Parameters.new("message" => expected_permitted.merge(evil: "true")) end before do @@ -240,7 +240,7 @@ RSpec.describe PermittedParams do subject { described_class.new(hash, user).message(project).to_h } it do - expect(subject).to eq(expected_allowed_params) + expect(subject).to eq(expected_permitted) end end end @@ -268,7 +268,7 @@ RSpec.describe PermittedParams do context "with empty status_code" do let(:hash) { { "status_code" => "" } } - let(:expected_allowed_params) { { "status_code" => nil } } + let(:expected_permitted) { { "status_code" => nil } } it_behaves_like "allows params" end @@ -316,7 +316,7 @@ RSpec.describe PermittedParams do context "with dependencies with empty values" do let(:hash) { { "dependencies" => ["", " "] } } - let(:expected_allowed_params) { { "dependencies" => [] } } + let(:expected_permitted) { { "dependencies" => [] } } it_behaves_like "allows params" end @@ -347,7 +347,7 @@ RSpec.describe PermittedParams do context "with empty status_code" do let(:hash) { { "status_code" => "" } } - let(:expected_allowed_params) { { "status_code" => nil } } + let(:expected_permitted) { { "status_code" => nil } } it_behaves_like "allows params" end @@ -368,7 +368,7 @@ RSpec.describe PermittedParams do { "type_ids" => ["1", "", "2"] } end - let(:expected_allowed_params) do + let(:expected_permitted) do [1, 2] end @@ -377,7 +377,7 @@ RSpec.describe PermittedParams do it do actual = described_class.new(params, user).send(attribute) - expect(actual).to eq(expected_allowed_params) + expect(actual).to eq(expected_permitted) end end @@ -597,7 +597,7 @@ RSpec.describe PermittedParams do { "activity_id" => "6", "active" => "1" } ] end - let(:expected_allowed_params) do + let(:expected_permitted) do [ ActionController::Parameters.new("activity_id" => "5", "active" => "0").permit!, ActionController::Parameters.new("activity_id" => "6", "active" => "1").permit! From 34610c9b47f629527267e5daef3e989ad8867861 Mon Sep 17 00:00:00 2001 From: Ivan Kuchin Date: Mon, 26 Jan 2026 21:37:51 +0100 Subject: [PATCH 112/293] more explicit expectation when params forbidden --- spec/models/permitted_params_spec.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/spec/models/permitted_params_spec.rb b/spec/models/permitted_params_spec.rb index 087d608dd28..61958670990 100644 --- a/spec/models/permitted_params_spec.rb +++ b/spec/models/permitted_params_spec.rb @@ -75,7 +75,10 @@ RSpec.describe PermittedParams do shared_examples_for "forbids params" do include_context "with prepare params comparison" - it { expect(subject).not_to eq(hash) } + it do + expected = defined?(expected_permitted) ? expected_permitted : {} + expect(subject).to eq(expected) + end end describe "#permit" do @@ -786,6 +789,7 @@ RSpec.describe PermittedParams do describe "invalid custom fields" do let(:hash) { { "custom_field_values" => { "blubs" => "5", "5" => { "1" => "2" } } } } + let(:expected_permitted) { { "custom_field_values" => {} } } it_behaves_like "forbids params" end @@ -912,11 +916,11 @@ RSpec.describe PermittedParams do } end - let(:expected_permitted_hash) do + let(:expected_permitted) do {} end - it { expect(subject).to eq(expected_permitted_hash) } + it_behaves_like "forbids params" end context "when fetching settings" do From fae99b0052260113d6f07cc918c907ea0d8ea3e2 Mon Sep 17 00:00:00 2001 From: Ivan Kuchin Date: Tue, 27 Jan 2026 15:59:15 +0100 Subject: [PATCH 113/293] add availability join for customizable journal --- .../journals/create_service/customizable.rb | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/app/services/journals/create_service/customizable.rb b/app/services/journals/create_service/customizable.rb index ef52d3c3915..9ba8f39f5f3 100644 --- a/app/services/journals/create_service/customizable.rb +++ b/app/services/journals/create_service/customizable.rb @@ -56,6 +56,7 @@ class Journals::CreateService custom_values.custom_field_id, #{normalize_newlines_sql('custom_values.value')} FROM custom_values + #{availability_join} WHERE #{only_if_created_sql} AND custom_values.customized_id = :journable_id @@ -72,16 +73,17 @@ class Journals::CreateService FROM ( SELECT - custom_field_id, - ARRAY_AGG(#{normalize_newlines_sql('custom_values.value')} ORDER BY value) AS value + custom_values.custom_field_id, + ARRAY_AGG(#{normalize_newlines_sql('custom_values.value')} ORDER BY value) AS value FROM - custom_values + custom_values + #{availability_join} WHERE custom_values.customized_id = :journable_id AND custom_values.customized_type = :customized_type AND custom_values.value != '' GROUP BY - custom_field_id + custom_values.custom_field_id ) current_values FULL JOIN ( @@ -100,5 +102,20 @@ class Journals::CreateService current_values.value IS DISTINCT FROM journal_values.value SQL end + + private + + def availability_join + return "" unless journable.is_a?(Project) + + <<~SQL # rubocop:disable Rails/SquishedSQLHeredocs + LEFT OUTER JOIN project_custom_field_project_mappings + ON project_custom_field_project_mappings.custom_field_id = custom_values.custom_field_id + AND project_custom_field_project_mappings.project_id = :journable_id + INNER JOIN custom_fields + ON custom_fields.id = custom_values.custom_field_id + AND (custom_fields.is_for_all = TRUE OR project_custom_field_project_mappings.project_id IS NOT NULL) + SQL + end end end From 06842e19cf636c04f3d1e63ed53f020661a42421 Mon Sep 17 00:00:00 2001 From: Ivan Kuchin Date: Wed, 28 Jan 2026 18:53:29 +0100 Subject: [PATCH 114/293] rename method in projects update contract to reduce confusion with_all_available_custom_fields_only => with_all_available_custom_fields, as it allows other changes too --- app/contracts/projects/update_contract.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/contracts/projects/update_contract.rb b/app/contracts/projects/update_contract.rb index d3397ed1d94..bb767a5de53 100644 --- a/app/contracts/projects/update_contract.rb +++ b/app/contracts/projects/update_contract.rb @@ -38,7 +38,7 @@ module Projects elsif allow_all_attributes # When all attributes are updated (API-only case), allow writing to all available custom # fields (including disabled ones) to maintain backward compatibility with the API. - with_all_available_custom_fields_only(super) + with_all_available_custom_fields(super) else [] end @@ -67,7 +67,7 @@ module Projects def with_available_custom_fields_only(changes) = changes & available_custom_fields.map(&:attribute_name) - def with_all_available_custom_fields_only(changes) + def with_all_available_custom_fields(changes) allowed_attributes = changes.grep_v(/^custom_field_/) allowed_attributes += changes & all_available_custom_fields.map(&:attribute_name) allowed_attributes From c747d981627c135541f346384e1277cf66125493 Mon Sep 17 00:00:00 2001 From: Ivan Kuchin Date: Wed, 28 Jan 2026 20:12:36 +0100 Subject: [PATCH 115/293] unify and mark with question boolean methods in update contract --- app/contracts/projects/update_contract.rb | 31 +++++++++++++---------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/app/contracts/projects/update_contract.rb b/app/contracts/projects/update_contract.rb index bb767a5de53..d2d3f4a37f9 100644 --- a/app/contracts/projects/update_contract.rb +++ b/app/contracts/projects/update_contract.rb @@ -31,11 +31,11 @@ module Projects class UpdateContract < BaseContract def writable_attributes - if allow_project_attributes_only + if allow_project_attributes_only? with_available_custom_fields_only(super) - elsif allow_edit_attributes_only + elsif allow_edit_attributes_only? without_custom_fields(super) - elsif allow_all_attributes + elsif allow_all_attributes? # When all attributes are updated (API-only case), allow writing to all available custom # fields (including disabled ones) to maintain backward compatibility with the API. with_all_available_custom_fields(super) @@ -46,21 +46,24 @@ module Projects private - def project_attributes_only = options[:project_attributes_only].present? + def project_attributes_only? = options[:project_attributes_only].present? - def edit_project = user.allowed_in_project?(:edit_project, model) + def allow_edit_project? = user.allowed_in_project?(:edit_project, model) - def edit_project_attributes = user.allowed_in_project?(:edit_project_attributes, model) + def allow_edit_project_attributes? = user.allowed_in_project?(:edit_project_attributes, model) - def allow_edit_attributes_only = edit_project && !project_attributes_only && !edit_project_attributes - - def allow_project_attributes_only - edit_project_attributes && (project_attributes_only || !edit_project) + def allow_edit_attributes_only? + allow_edit_project? && !project_attributes_only? && !allow_edit_project_attributes? end - def allow_all_attributes - (edit_project && edit_project_attributes && !project_attributes_only) || - (changed_by_user == ["active"]) # Allow archiving, permission checked in manage_permission + def allow_project_attributes_only? + allow_edit_project_attributes? && (project_attributes_only? || !allow_edit_project?) + end + + def allow_all_attributes? + return true if allow_edit_project? && allow_edit_project_attributes? && !project_attributes_only? + + changed_by_user == ["active"] # Allow archiving, permission checked in manage_permission end def without_custom_fields(changes) = changes.grep_v(/^custom_field_/) @@ -76,7 +79,7 @@ module Projects def manage_permission if changed_by_user == ["active"] :archive_project - elsif project_attributes_only + elsif project_attributes_only? :edit_project_attributes else # if "active" is changed, :archive_project permission will also be From 84bb42825ee29dd42f599afcd7990fb9ed5ad39f Mon Sep 17 00:00:00 2001 From: Ivan Kuchin Date: Thu, 29 Jan 2026 17:04:50 +0100 Subject: [PATCH 116/293] rename ShowComponent to SectionComponent for clarity --- .../{show_component.html.erb => section_component.html.erb} | 0 .../{show_component.rb => section_component.rb} | 2 +- .../project_custom_fields/side_panel_component.html.erb | 2 +- .../{show_component_spec.rb => section_component_spec.rb} | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename modules/overviews/app/components/overviews/project_custom_fields/{show_component.html.erb => section_component.html.erb} (100%) rename modules/overviews/app/components/overviews/project_custom_fields/{show_component.rb => section_component.rb} (97%) rename modules/overviews/spec/components/overviews/project_custom_fields/{show_component_spec.rb => section_component_spec.rb} (96%) diff --git a/modules/overviews/app/components/overviews/project_custom_fields/show_component.html.erb b/modules/overviews/app/components/overviews/project_custom_fields/section_component.html.erb similarity index 100% rename from modules/overviews/app/components/overviews/project_custom_fields/show_component.html.erb rename to modules/overviews/app/components/overviews/project_custom_fields/section_component.html.erb diff --git a/modules/overviews/app/components/overviews/project_custom_fields/show_component.rb b/modules/overviews/app/components/overviews/project_custom_fields/section_component.rb similarity index 97% rename from modules/overviews/app/components/overviews/project_custom_fields/show_component.rb rename to modules/overviews/app/components/overviews/project_custom_fields/section_component.rb index dcf8abc9b71..fd96cc3c912 100644 --- a/modules/overviews/app/components/overviews/project_custom_fields/show_component.rb +++ b/modules/overviews/app/components/overviews/project_custom_fields/section_component.rb @@ -30,7 +30,7 @@ module Overviews module ProjectCustomFields - class ShowComponent < ApplicationComponent + class SectionComponent < ApplicationComponent include ApplicationHelper include OpPrimer::ComponentHelpers include OpTurbo::Streamable diff --git a/modules/overviews/app/components/overviews/project_custom_fields/side_panel_component.html.erb b/modules/overviews/app/components/overviews/project_custom_fields/side_panel_component.html.erb index be6b76198ad..17578722f60 100644 --- a/modules/overviews/app/components/overviews/project_custom_fields/side_panel_component.html.erb +++ b/modules/overviews/app/components/overviews/project_custom_fields/side_panel_component.html.erb @@ -9,7 +9,7 @@ ) do |panel| available_project_custom_fields_grouped_by_section.each do |project_custom_field_section, project_custom_fields| panel.with_section( - Overviews::ProjectCustomFields::ShowComponent.new( + Overviews::ProjectCustomFields::SectionComponent.new( project: @project, project_custom_field_section:, project_custom_fields: project_custom_fields diff --git a/modules/overviews/spec/components/overviews/project_custom_fields/show_component_spec.rb b/modules/overviews/spec/components/overviews/project_custom_fields/section_component_spec.rb similarity index 96% rename from modules/overviews/spec/components/overviews/project_custom_fields/show_component_spec.rb rename to modules/overviews/spec/components/overviews/project_custom_fields/section_component_spec.rb index a51c3091d77..bc83efe1729 100644 --- a/modules/overviews/spec/components/overviews/project_custom_fields/show_component_spec.rb +++ b/modules/overviews/spec/components/overviews/project_custom_fields/section_component_spec.rb @@ -30,7 +30,7 @@ require "rails_helper" -RSpec.describe Overviews::ProjectCustomFields::ShowComponent, type: :component do +RSpec.describe Overviews::ProjectCustomFields::SectionComponent, type: :component do include Rails.application.routes.url_helpers def render_component(...) From 7e91103076d76e7e4f9daf35866f79af942f0108 Mon Sep 17 00:00:00 2001 From: Ivan Kuchin Date: Fri, 30 Jan 2026 16:48:54 +0100 Subject: [PATCH 117/293] change custom_values_for_custom_field to accept custom field or id and as positional argument --- .../inputs/base/autocomplete/multi_value_input.rb | 2 +- .../acts_as_customizable/lib/acts_as_customizable.rb | 8 +++++--- spec/features/projects/lists/table_spec.rb | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/forms/custom_fields/inputs/base/autocomplete/multi_value_input.rb b/app/forms/custom_fields/inputs/base/autocomplete/multi_value_input.rb index 13c863c0c54..d1695fcb939 100644 --- a/app/forms/custom_fields/inputs/base/autocomplete/multi_value_input.rb +++ b/app/forms/custom_fields/inputs/base/autocomplete/multi_value_input.rb @@ -53,7 +53,7 @@ class CustomFields::Inputs::Base::Autocomplete::MultiValueInput < CustomFields:: end def custom_values - @custom_values ||= @object.custom_values_for_custom_field(id: @custom_field.id) + @custom_values ||= @object.custom_values_for_custom_field(@custom_field) end def invalid? diff --git a/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb b/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb index 878836074ba..a6840af4533 100644 --- a/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb +++ b/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb @@ -127,7 +127,7 @@ module Redmine return unless values.is_a?(Hash) && values.any? values.with_indifferent_access.each do |custom_field_id, val| - existing_cv_by_value = custom_values_for_custom_field(id: custom_field_id, all: true) + existing_cv_by_value = custom_values_for_custom_field(custom_field_id, all: true) .group_by(&:value) .transform_values(&:first) new_values = Array(val).map { |v| v.respond_to?(:id) ? v.id.to_s : v.to_s } @@ -140,8 +140,10 @@ module Redmine end end - def custom_values_for_custom_field(id:, all: false) - custom_field_values(all:).select { |cv| cv.custom_field_id == id.to_i } + def custom_values_for_custom_field(custom_field_or_id, all: false) + id = custom_field_or_id.is_a?(CustomField) ? custom_field_or_id.id : custom_field_or_id.to_i + + custom_field_values(all:).select { |cv| cv.custom_field_id == id } end def custom_field_values(all: false) = cached_custom_field_values[all ? :all_available : :available] diff --git a/spec/features/projects/lists/table_spec.rb b/spec/features/projects/lists/table_spec.rb index a9d534efa91..2c8a32c8edb 100644 --- a/spec/features/projects/lists/table_spec.rb +++ b/spec/features/projects/lists/table_spec.rb @@ -169,7 +169,7 @@ RSpec.describe "Projects lists table display and actions", :js, with_settings: { expect(page) .to have_no_text( development_project.custom_values_for_custom_field( - id: custom_field.id, + custom_field, all: true ).first.value ) From 13bfd7cf24a55fba9b359d3e3781dbcb5ffb0a45 Mon Sep 17 00:00:00 2001 From: Ivan Kuchin Date: Tue, 3 Feb 2026 17:36:00 +0100 Subject: [PATCH 118/293] use anchors in all regexps for register_journal_formatted_fields --- app/models/project.rb | 6 +++--- app/models/work_package/journalized.rb | 6 +++--- modules/meeting/app/models/meeting/journalized.rb | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 05c86a7635c..456b615bc54 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -174,9 +174,9 @@ class Project < ApplicationRecord register_journal_formatted_fields "status_code", formatter_key: :project_status_code register_journal_formatted_fields "public", formatter_key: :visibility register_journal_formatted_fields "parent_id", formatter_key: :subproject_named_association - register_journal_formatted_fields /custom_fields_\d+/, formatter_key: :custom_field - register_journal_formatted_fields /^project_phase_\d+_active$/, formatter_key: :project_phase_active - register_journal_formatted_fields /^project_phase_\d+_date_range$/, formatter_key: :project_phase_dates + register_journal_formatted_fields /\Acustom_fields_\d+\z/, formatter_key: :custom_field + register_journal_formatted_fields /\Aproject_phase_\d+_active\z/, formatter_key: :project_phase_active + register_journal_formatted_fields /\Aproject_phase_\d+_date_range\z/, formatter_key: :project_phase_dates has_paper_trail diff --git a/app/models/work_package/journalized.rb b/app/models/work_package/journalized.rb index 518b6919d64..9f81b0f74bc 100644 --- a/app/models/work_package/journalized.rb +++ b/app/models/work_package/journalized.rb @@ -96,11 +96,11 @@ module WorkPackage::Journalized register_journal_formatted_fields "done_ratio", "derived_done_ratio", formatter_key: :percentage register_journal_formatted_fields "description", formatter_key: :diff register_journal_formatted_fields "schedule_manually", formatter_key: :schedule_manually - register_journal_formatted_fields /attachments_?\d+/, formatter_key: :attachment - register_journal_formatted_fields /custom_fields_\d+/, formatter_key: :custom_field + register_journal_formatted_fields /\Aattachments_?\d+\z/, formatter_key: :attachment + register_journal_formatted_fields /\Acustom_fields_\d+\z/, formatter_key: :custom_field register_journal_formatted_fields "ignore_non_working_days", formatter_key: :ignore_non_working_days register_journal_formatted_fields "cause", formatter_key: :cause - register_journal_formatted_fields /file_links_?\d+/, formatter_key: :file_link + register_journal_formatted_fields /\Afile_links_?\d+\z/, formatter_key: :file_link register_journal_formatted_fields "project_phase_definition_id", formatter_key: :project_phase_definition # Joined diff --git a/modules/meeting/app/models/meeting/journalized.rb b/modules/meeting/app/models/meeting/journalized.rb index 00c1105732f..e4db65d8fd0 100644 --- a/modules/meeting/app/models/meeting/journalized.rb +++ b/modules/meeting/app/models/meeting/journalized.rb @@ -49,10 +49,10 @@ module Meeting::Journalized register_journal_formatted_fields "state", formatter_key: :meeting_state register_journal_formatted_fields "duration", formatter_key: :agenda_item_duration - register_journal_formatted_fields /agenda_items_\d+_notes/, formatter_key: :agenda_item_diff - register_journal_formatted_fields /agenda_items_\d+_title/, formatter_key: :agenda_item_title - register_journal_formatted_fields /agenda_items_\d+_duration_in_minutes/, formatter_key: :agenda_item_duration + register_journal_formatted_fields /\Aagenda_items_\d+_notes\z/, formatter_key: :agenda_item_diff + register_journal_formatted_fields /\Aagenda_items_\d+_title\z/, formatter_key: :agenda_item_title + register_journal_formatted_fields /\Aagenda_items_\d+_duration_in_minutes\z/, formatter_key: :agenda_item_duration register_journal_formatted_fields "position", formatter_key: :agenda_item_position - register_journal_formatted_fields /agenda_items_\d+_work_package_id/, formatter_key: :meeting_work_package_id + register_journal_formatted_fields /\Aagenda_items_\d+_work_package_id\z/, formatter_key: :meeting_work_package_id end end From bf1182ba7c19a9de4440679a4c431742ead8e8b3 Mon Sep 17 00:00:00 2001 From: Ivan Kuchin Date: Wed, 4 Feb 2026 19:48:46 +0100 Subject: [PATCH 119/293] refactor custom_field_values= to calm down rubocop --- .../lib/acts_as_customizable.rb | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb b/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb index a6840af4533..02200694da7 100644 --- a/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb +++ b/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb @@ -126,17 +126,13 @@ module Redmine def custom_field_values=(values) return unless values.is_a?(Hash) && values.any? - values.with_indifferent_access.each do |custom_field_id, val| + values.with_indifferent_access.each do |custom_field_id, new_values| existing_cv_by_value = custom_values_for_custom_field(custom_field_id, all: true) .group_by(&:value) .transform_values(&:first) - new_values = Array(val).map { |v| v.respond_to?(:id) ? v.id.to_s : v.to_s } + next if existing_cv_by_value.empty? - if existing_cv_by_value.any? - assign_new_values custom_field_id, existing_cv_by_value, new_values - delete_obsolete_custom_values existing_cv_by_value, new_values - handle_minimum_custom_value custom_field_id, existing_cv_by_value, new_values - end + update_custom_value(custom_field_id, existing_cv_by_value, new_values) end end @@ -435,6 +431,14 @@ module Redmine touch if !saved_changes? && custom_values.loaded? && (custom_values.any?(&:saved_changes?) || custom_value_destroyed) end + def update_custom_value(custom_field_id, existing_cv_by_value, new_values) + new_values = Array(new_values).map { |v| v.respond_to?(:id) ? v.id.to_s : v.to_s } + + assign_new_values(custom_field_id, existing_cv_by_value, new_values) + delete_obsolete_custom_values(existing_cv_by_value, new_values) + handle_minimum_custom_value(custom_field_id, existing_cv_by_value, new_values) + end + def assign_new_values(custom_field_id, existing_cv_by_value, new_values) (new_values - existing_cv_by_value.keys).each do |new_value| add_custom_value(custom_field_id, new_value) From e5f1c37719450c46cd3c280ad076a9a7fa4dcdb5 Mon Sep 17 00:00:00 2001 From: Ivan Kuchin Date: Wed, 4 Feb 2026 21:21:34 +0100 Subject: [PATCH 120/293] inline shared context included in all contexts --- .../projects/project_acts_as_journalized_spec.rb | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/spec/models/projects/project_acts_as_journalized_spec.rb b/spec/models/projects/project_acts_as_journalized_spec.rb index 32f010f0c38..9fafbb18b8e 100644 --- a/spec/models/projects/project_acts_as_journalized_spec.rb +++ b/spec/models/projects/project_acts_as_journalized_spec.rb @@ -124,10 +124,8 @@ RSpec.describe Project, "acts_as_journalized" do end let(:custom_field_key) { "custom_fields_#{custom_field.id}" } - shared_context "for project with new custom value" do - before do - project.update(custom_values: [custom_value]) - end + before do + project.update(custom_values: [custom_value]) end shared_examples "contains no change for disabled custom field" do @@ -141,8 +139,6 @@ RSpec.describe Project, "acts_as_journalized" do end context "for new custom value" do - include_context "for project with new custom value" - it "contains the new custom value change" do expect(project.last_journal.details) .to include(custom_field_key => [nil, custom_value.value]) @@ -152,8 +148,6 @@ RSpec.describe Project, "acts_as_journalized" do end context "for updated custom value" do - include_context "for project with new custom value" - let(:modified_custom_value) do build(:custom_value, value: "some modified value for project custom field", @@ -173,8 +167,6 @@ RSpec.describe Project, "acts_as_journalized" do end context "when project saved without any changes" do - include_context "for project with new custom value" - let(:unmodified_custom_value) do build(:custom_value, value: custom_value.value, @@ -189,8 +181,6 @@ RSpec.describe Project, "acts_as_journalized" do end context "when custom value removed" do - include_context "for project with new custom value" - before do project.update(custom_values: []) end From c6ebcf3f1c59bb2f0663ed33e1b7a042aca09f94 Mon Sep 17 00:00:00 2001 From: Ivan Kuchin Date: Wed, 4 Feb 2026 21:36:41 +0100 Subject: [PATCH 121/293] move more code to shared example in project custom field journaling spec --- .../project_acts_as_journalized_spec.rb | 50 +++++++++---------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/spec/models/projects/project_acts_as_journalized_spec.rb b/spec/models/projects/project_acts_as_journalized_spec.rb index 9fafbb18b8e..68beb3d49b7 100644 --- a/spec/models/projects/project_acts_as_journalized_spec.rb +++ b/spec/models/projects/project_acts_as_journalized_spec.rb @@ -128,23 +128,26 @@ RSpec.describe Project, "acts_as_journalized" do project.update(custom_values: [custom_value]) end - shared_examples "contains no change for disabled custom field" do - before do - project.project_custom_field_project_mappings.where(custom_field_id: custom_field.id).delete_all + shared_examples "contains the expected change" do + it "contains the expected change" do + expect(project.last_journal.details).to include(custom_field_key => expected_change) end - it "contains no change for the disabled custom field" do - expect(project.last_journal.details).not_to have_key(custom_field_key) + context "for disabled custom field" do + before do + project.project_custom_field_project_mappings.where(custom_field_id: custom_field.id).delete_all + end + + it "contains no change for the disabled custom field" do + expect(project.last_journal.details).not_to have_key(custom_field_key) + end end end context "for new custom value" do - it "contains the new custom value change" do - expect(project.last_journal.details) - .to include(custom_field_key => [nil, custom_value.value]) - end + let(:expected_change) { [nil, custom_value.value] } - it_behaves_like "contains no change for disabled custom field" + include_examples "contains the expected change" end context "for updated custom value" do @@ -153,17 +156,23 @@ RSpec.describe Project, "acts_as_journalized" do value: "some modified value for project custom field", custom_field:) end + let(:expected_change) { [custom_value.value, modified_custom_value.value] } before do project.update(custom_values: [modified_custom_value]) end - it "contains the change from previous value to updated value" do - expect(project.last_journal.details) - .to include(custom_field_key => [custom_value.value, modified_custom_value.value]) + include_examples "contains the expected change" + end + + context "when custom value removed" do + let(:expected_change) { [custom_value.value, nil] } + + before do + project.update(custom_values: []) end - it_behaves_like "contains no change for disabled custom field" + include_examples "contains the expected change" end context "when project saved without any changes" do @@ -179,19 +188,6 @@ RSpec.describe Project, "acts_as_journalized" do it { expect { project.save! }.not_to change(Journal, :count) } end - - context "when custom value removed" do - before do - project.update(custom_values: []) - end - - it "contains the change from previous value to nil" do - expect(project.last_journal.details) - .to include(custom_field_key => [custom_value.value, nil]) - end - - it_behaves_like "contains no change for disabled custom field" - end end describe "phases", with_settings: { journal_aggregation_time_minutes: 0 } do From 9137ae0d009b229bd5e0f27dec0f567e8307935b Mon Sep 17 00:00:00 2001 From: Ivan Kuchin Date: Wed, 4 Feb 2026 21:37:04 +0100 Subject: [PATCH 122/293] add test for handling is_for_all separately --- .../projects/project_acts_as_journalized_spec.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/spec/models/projects/project_acts_as_journalized_spec.rb b/spec/models/projects/project_acts_as_journalized_spec.rb index 68beb3d49b7..41de344b850 100644 --- a/spec/models/projects/project_acts_as_journalized_spec.rb +++ b/spec/models/projects/project_acts_as_journalized_spec.rb @@ -141,6 +141,16 @@ RSpec.describe Project, "acts_as_journalized" do it "contains no change for the disabled custom field" do expect(project.last_journal.details).not_to have_key(custom_field_key) end + + context "if custom field is marked for all" do + before do + custom_field.update_attribute(:is_for_all, true) + end + + it "contains the expected change" do + expect(project.last_journal.details).to include(custom_field_key => expected_change) + end + end end end From 7dd3a22fedf33a4d54b34dea39a90dd624d5fc01 Mon Sep 17 00:00:00 2001 From: Yauheni Suhakou Date: Thu, 5 Feb 2026 15:07:07 +0100 Subject: [PATCH 123/293] [71321] Update system requirements docs page (#21873) * [#71321] Update system requirements docs page https://community.openproject.org/work_packages/71321 --- .../system-requirements/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/installation-and-operations/system-requirements/README.md b/docs/installation-and-operations/system-requirements/README.md index 12611d1fb7b..ed3c7c27d2c 100644 --- a/docs/installation-and-operations/system-requirements/README.md +++ b/docs/installation-and-operations/system-requirements/README.md @@ -34,7 +34,7 @@ OpenProject currently requires some bundled extensions, that should be available - [btree_gist: GiST operator classes with B-tree behavior](https://www.postgresql.org/docs/current/btree-gist.html) - [unaccent: a text search dictionary which removes diacritics](https://www.postgresql.org/docs/current/unaccent.html) -Additionally, OpenProject will try to create a [custom collation](https://www.postgresql.org/docs/current/collation.html) for version sorting that depends on `und-u-kn-true` ICU collation. +Additionally, OpenProject will try to create a [custom collation](https://www.postgresql.org/docs/current/collation.html) for version sorting that depends on `und-u-kn-true` ICU collation. ## Scaling requirements @@ -214,6 +214,7 @@ OpenProject supports the latest versions of the major browsers. * [Nextcloud 30](https://nextcloud.com/changelog/#latest30) * [Nextcloud 31](https://nextcloud.com/changelog/#latest31) +* [Nextcloud 32](https://nextcloud.com/changelog/#latest32) > [!TIP] > @@ -228,13 +229,13 @@ OpenProject supports the latest versions of the major browsers. ##### OpenProject integration -* [OpenProject Integration 2.10.0](https://github.com/nextcloud/integration_openproject/releases/tag/v2.10.0) +* [OpenProject Integration 2.11.1](https://github.com/nextcloud/integration_openproject/releases/tag/v2.11.1) ##### Team folders If you want to use the feature of [automatically managed project folders](../../system-admin-guide/integrations/nextcloud/#4-automatically-managed-project-folders) you need to install the [Team folders](https://apps.nextcloud.com/apps/groupfolders) app in Nextcloud (formerly Group folders). -* [Team folders 19.1.7](https://github.com/nextcloud/groupfolders/releases/tag/v19.1.7) +* [Team folders 19.1.14](https://github.com/nextcloud/groupfolders/releases/tag/v19.1.14) ### Keycloak token exchange From 4e3f8121d15a854513fed61aa271f648a5a2c3b5 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Thu, 5 Feb 2026 11:28:40 -0300 Subject: [PATCH 124/293] Unify empty backlog blankslate messages Use a single blankslate message for all backlog types with dynamic sprint name interpolation, e.g., "Sprint 1 is empty" or "Product Backlog is empty". Co-Authored-By: Claude Opus 4.5 --- .../backlogs/backlog_component.html.erb | 21 ++++++------------- modules/backlogs/config/locales/en.yml | 8 ++----- .../backlogs/backlog_component_spec.rb | 2 +- 3 files changed, 9 insertions(+), 22 deletions(-) diff --git a/modules/backlogs/app/components/backlogs/backlog_component.html.erb b/modules/backlogs/app/components/backlogs/backlog_component.html.erb index 9ee1cf8577f..cbbe2339740 100644 --- a/modules/backlogs/app/components/backlogs/backlog_component.html.erb +++ b/modules/backlogs/app/components/backlogs/backlog_component.html.erb @@ -34,21 +34,12 @@ See COPYRIGHT and LICENSE files for more details. <% end %> <% if backlog.stories.empty? %> <% border_box.with_row(data: { empty_list_item: true }) do %> - <% if backlog.sprint_backlog? %> - <%= - render Primer::Beta::Blankslate.new(role: "status", aria: { live: "polite" }) do |blankslate| - blankslate.with_heading(tag: :h4).with_content(t(".sprint_backlog.blankslate_title")) - blankslate.with_description_content(t(".sprint_backlog.blankslate_description")) - end - %> - <% else %> - <%= - render Primer::Beta::Blankslate.new(role: "status", aria: { live: "polite" }) do |blankslate| - blankslate.with_heading(tag: :h4).with_content(t(".product_backlog.blankslate_title")) - blankslate.with_description_content(t(".product_backlog.blankslate_description")) - end - %> - <% end %> + <%= + render Primer::Beta::Blankslate.new(role: "status", aria: { live: "polite" }) do |blankslate| + blankslate.with_heading(tag: :h4).with_content(t(".blankslate_title", name: sprint.name)) + blankslate.with_description_content(t(".blankslate_description")) + end + %> <% end %> <% end %> <% backlog.stories.each do |story| %> diff --git a/modules/backlogs/config/locales/en.yml b/modules/backlogs/config/locales/en.yml index 7183466ffd3..a44339c6831 100644 --- a/modules/backlogs/config/locales/en.yml +++ b/modules/backlogs/config/locales/en.yml @@ -86,12 +86,8 @@ en: button_update_backlogs: "Update backlogs module" backlog_component: - sprint_backlog: - blankslate_title: "Sprint Backlog is empty" - blankslate_description: "No items planned yet. Drag items here to add them to the Sprint." - product_backlog: - blankslate_title: "Product Backlog is empty" - blankslate_description: "There is no upcoming work defined in the Product Backlog" + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" diff --git a/modules/backlogs/spec/components/backlogs/backlog_component_spec.rb b/modules/backlogs/spec/components/backlogs/backlog_component_spec.rb index f3bab7fb4a2..084b9f232dc 100644 --- a/modules/backlogs/spec/components/backlogs/backlog_component_spec.rb +++ b/modules/backlogs/spec/components/backlogs/backlog_component_spec.rb @@ -136,7 +136,7 @@ RSpec.describe Backlogs::BacklogComponent, type: :component do let(:stories) { [] } let(:rendered_component) { render_component } - it_behaves_like "rendering Blank Slate", heading: "Sprint Backlog is empty" + it_behaves_like "rendering Blank Slate", heading: "Sprint 1 is empty" end end end From abcf52f6da3faf99d8c78f53abbea0520d10927d Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Thu, 5 Feb 2026 11:32:36 -0300 Subject: [PATCH 125/293] Fix story row classes test expectations Update test to match actual class names after hover/focus color changes in dc9d9cb6800. Co-Authored-By: Claude Opus 4.5 --- .../spec/components/backlogs/backlog_component_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/backlogs/spec/components/backlogs/backlog_component_spec.rb b/modules/backlogs/spec/components/backlogs/backlog_component_spec.rb index 084b9f232dc..b6f759bf3ee 100644 --- a/modules/backlogs/spec/components/backlogs/backlog_component_spec.rb +++ b/modules/backlogs/spec/components/backlogs/backlog_component_spec.rb @@ -126,8 +126,8 @@ RSpec.describe Backlogs::BacklogComponent, type: :component do render_component story_row = page.find(".Box-row[id='story_#{story1.id}']") - expect(story_row[:class]).to include("Box-row--hover-gray") - expect(story_row[:class]).to include("Box-row--focus-blue") + expect(story_row[:class]).to include("Box-row--hover-blue") + expect(story_row[:class]).to include("Box-row--focus-gray") expect(story_row[:class]).to include("Box-row--clickable") end end From 7d54e81b1cb1bcd7196f3490eef6a0c591bc4ac0 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Thu, 5 Feb 2026 12:04:36 -0300 Subject: [PATCH 126/293] Make story points display responsive Hide "points" label text when container width is restricted (reuses container query from 61ff200bdc7). Only the numeric value is shown in narrow viewports. - Replace Primer::Beta::Truncate with Primer::Beta::Text for points - Add points_label translation for just the label portion - Remove unused backlogs.points translation - Add font-variant-numeric: tabular-nums for numeric alignment - Override grid column min-width in narrow container query Co-Authored-By: Claude Opus 4.5 --- .../src/assets/sass/backlogs/_master_backlog.sass | 12 ++++++++++++ .../backlogs/backlog_header_component.html.erb | 5 +++-- .../app/components/backlogs/story_component.html.erb | 5 +++-- modules/backlogs/config/locales/en.yml | 6 +++--- .../backlogs/backlog_header_component_spec.rb | 4 ++-- .../spec/components/backlogs/story_component_spec.rb | 8 ++++---- 6 files changed, 27 insertions(+), 13 deletions(-) diff --git a/frontend/src/assets/sass/backlogs/_master_backlog.sass b/frontend/src/assets/sass/backlogs/_master_backlog.sass index ac2d8072d2e..43d6a031d00 100644 --- a/frontend/src/assets/sass/backlogs/_master_backlog.sass +++ b/frontend/src/assets/sass/backlogs/_master_backlog.sass @@ -27,6 +27,7 @@ */ $op-backlogs-header--points-min-width: 5rem +$op-backlogs-header--points-min-width-narrow: 2rem .op-backlogs-header display: grid @@ -39,6 +40,7 @@ $op-backlogs-header--points-min-width: 5rem .op-backlogs-header--points margin-left: var(--stack-gap-normal) + font-variant-numeric: tabular-nums .op-backlogs-header--menu margin-left: var(--stack-gap-normal) @@ -94,6 +96,7 @@ $op-backlogs-header--points-min-width: 5rem .op-backlogs-story--points margin-left: var(--stack-gap-normal) + font-variant-numeric: tabular-nums .op-backlogs-story--menu margin-left: var(--stack-gap-normal) @@ -133,3 +136,12 @@ $op-backlogs-header--points-min-width: 5rem @container backlogsListsContainer (max-width: 543px) .op-backlogs-container flex-direction: column + + .op-backlogs-points-label + display: none + + .op-backlogs-header + grid-template-columns: 1fr minmax($op-backlogs-header--points-min-width-narrow, max-content) auto + + .op-backlogs-story + grid-template-columns: var(--control-xsmall-size) 1fr minmax($op-backlogs-header--points-min-width-narrow, max-content) auto diff --git a/modules/backlogs/app/components/backlogs/backlog_header_component.html.erb b/modules/backlogs/app/components/backlogs/backlog_header_component.html.erb index b9939738804..9d6f47b1518 100644 --- a/modules/backlogs/app/components/backlogs/backlog_header_component.html.erb +++ b/modules/backlogs/app/components/backlogs/backlog_header_component.html.erb @@ -59,14 +59,15 @@ See COPYRIGHT and LICENSE files for more details. <% grid.with_area(:points) do %> <%= render( - Primer::Beta::Truncate.new( + Primer::Beta::Text.new( color: :subtle, classes: "velocity", aria: { live: "polite" } ) ) do %> - <%= t(:"backlogs.points", count: story_points) %> + <%= story_points %> + <%= t(:"backlogs.points_label", count: story_points) %> <% end %> <% end %> diff --git a/modules/backlogs/app/components/backlogs/story_component.html.erb b/modules/backlogs/app/components/backlogs/story_component.html.erb index aff0f073f4c..d18cd643c73 100644 --- a/modules/backlogs/app/components/backlogs/story_component.html.erb +++ b/modules/backlogs/app/components/backlogs/story_component.html.erb @@ -48,8 +48,9 @@ See COPYRIGHT and LICENSE files for more details. <% end %> <% grid.with_area(:points) do %> - <%= render(Primer::Beta::Truncate.new(color: :subtle)) do %> - <%= t(:"backlogs.points", count: story_points) %> + <%= render(Primer::Beta::Text.new(color: :subtle)) do %> + <%= story_points %> + <%= t(:"backlogs.points_label", count: story_points) %> <% end %> <% end %> diff --git a/modules/backlogs/config/locales/en.yml b/modules/backlogs/config/locales/en.yml index a44339c6831..002b73f14c2 100644 --- a/modules/backlogs/config/locales/en.yml +++ b/modules/backlogs/config/locales/en.yml @@ -65,9 +65,9 @@ en: caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." work_package_is_closed: "Work package is done, when" label_is_done_status: "Status %{status_name} means done" - points: - one: "%{count} point" - other: "%{count} points" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "Positions could not be rebuilt." positions_rebuilt_successfully: "Positions rebuilt successfully." rebuild: "Rebuild" diff --git a/modules/backlogs/spec/components/backlogs/backlog_header_component_spec.rb b/modules/backlogs/spec/components/backlogs/backlog_header_component_spec.rb index ded99a4092f..40c25c764c3 100644 --- a/modules/backlogs/spec/components/backlogs/backlog_header_component_spec.rb +++ b/modules/backlogs/spec/components/backlogs/backlog_header_component_spec.rb @@ -111,7 +111,7 @@ RSpec.describe Backlogs::BacklogHeaderComponent, type: :component do render_component # 5 + 3 + 0 = 8 points - expect(page).to have_text("8 points") + expect(page).to have_text("8 points", normalize_ws: true) end it "renders collapse/expand chevrons" do @@ -140,7 +140,7 @@ RSpec.describe Backlogs::BacklogHeaderComponent, type: :component do it "shows 0 points" do render_component - expect(page).to have_text("0 points") + expect(page).to have_text("0 points", normalize_ws: true) end end diff --git a/modules/backlogs/spec/components/backlogs/story_component_spec.rb b/modules/backlogs/spec/components/backlogs/story_component_spec.rb index e09562c2701..5481e6bfcbf 100644 --- a/modules/backlogs/spec/components/backlogs/story_component_spec.rb +++ b/modules/backlogs/spec/components/backlogs/story_component_spec.rb @@ -89,7 +89,7 @@ RSpec.describe Backlogs::StoryComponent, type: :component do it "shows story points" do render_component - expect(page).to have_text("5 points") + expect(page).to have_text("5 points", normalize_ws: true) end it "renders StoryMenuComponent" do @@ -106,7 +106,7 @@ RSpec.describe Backlogs::StoryComponent, type: :component do it "shows 0 points" do render_component - expect(page).to have_text("0 points") + expect(page).to have_text("0 points", normalize_ws: true) end end @@ -116,7 +116,7 @@ RSpec.describe Backlogs::StoryComponent, type: :component do it "shows 0 points" do render_component - expect(page).to have_text("0 points") + expect(page).to have_text("0 points", normalize_ws: true) end end @@ -126,7 +126,7 @@ RSpec.describe Backlogs::StoryComponent, type: :component do it "shows 1 point (singular)" do render_component - expect(page).to have_text("1 point") + expect(page).to have_text("1 point", normalize_ws: true) end end end From 4d528a486ec904017a2f9d006eb6003f953eef52 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Thu, 5 Feb 2026 12:14:12 -0300 Subject: [PATCH 127/293] Replace media queries with container queries Convert remaining @media queries to @container queries for consistent responsive behavior that adapts to the backlogs container width rather than viewport width. - Move collapsible narrow styles into container query - Move header form wide styles into container query - Reorder rules to match main stylesheet order Co-Authored-By: Claude Opus 4.5 --- .../assets/sass/backlogs/_master_backlog.sass | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/frontend/src/assets/sass/backlogs/_master_backlog.sass b/frontend/src/assets/sass/backlogs/_master_backlog.sass index 43d6a031d00..ec90b5c3055 100644 --- a/frontend/src/assets/sass/backlogs/_master_backlog.sass +++ b/frontend/src/assets/sass/backlogs/_master_backlog.sass @@ -71,15 +71,6 @@ $op-backlogs-header--points-min-width-narrow: 2rem &[hidden] opacity: 0 -@media screen and (max-width: $breakpoint-sm) - .op-backlogs-collapsible - flex-direction: column - align-items: flex-start - - &--description - [data-collapsed] & - display: none - .op-backlogs-story display: grid grid-template-columns: var(--control-xsmall-size) 1fr minmax($op-backlogs-header--points-min-width, max-content) auto @@ -106,16 +97,6 @@ $op-backlogs-header--points-min-width-narrow: 2rem word-wrap: break-word overflow-wrap: break-word -@media screen and (min-width: $breakpoint-sm) - .op-backlogs-header-form - .FormControl-spacingWrapper - flex-direction: row - column-gap: 0.5rem - - & > :first-child - flex: 1 1 auto - min-width: 33% - .op-backlogs-page display: block container-name: backlogsListsContainer @@ -133,15 +114,35 @@ $op-backlogs-header--points-min-width-narrow: 2rem flex: 1 1 100% overflow: hidden +// Note: Using hardcoded values instead of $breakpoint-sm because +// Sass doesn't interpolate variables in @container query conditions +@container backlogsListsContainer (min-width: 544px) + .op-backlogs-header-form + .FormControl-spacingWrapper + flex-direction: row + column-gap: 0.5rem + + & > :first-child + flex: 1 1 auto + min-width: 33% + @container backlogsListsContainer (max-width: 543px) - .op-backlogs-container + .op-backlogs-header + grid-template-columns: 1fr minmax($op-backlogs-header--points-min-width-narrow, max-content) auto + + .op-backlogs-collapsible flex-direction: column + align-items: flex-start + + &--description + [data-collapsed] & + display: none .op-backlogs-points-label display: none - .op-backlogs-header - grid-template-columns: 1fr minmax($op-backlogs-header--points-min-width-narrow, max-content) auto - .op-backlogs-story grid-template-columns: var(--control-xsmall-size) 1fr minmax($op-backlogs-header--points-min-width-narrow, max-content) auto + + .op-backlogs-container + flex-direction: column From 00864f331e3eb10c6ca0ecd779cf733baf78ba2a Mon Sep 17 00:00:00 2001 From: ulferts Date: Thu, 5 Feb 2026 16:17:54 +0100 Subject: [PATCH 128/293] remove outdated guard --- app/services/types/apply_patterns.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/services/types/apply_patterns.rb b/app/services/types/apply_patterns.rb index b01490549d3..600d8a10278 100644 --- a/app/services/types/apply_patterns.rb +++ b/app/services/types/apply_patterns.rb @@ -37,8 +37,6 @@ module Types def apply_patterns(model, save: true) model.type&.enabled_patterns&.each do |key, pattern| - next if model.changed_attribute_keys.include?(key) - model.public_send(:"#{key}=", pattern.resolve(model)) end From a1032a55a26a99dede2678dec7a98222fe694643 Mon Sep 17 00:00:00 2001 From: ulferts Date: Thu, 5 Feb 2026 17:00:12 +0100 Subject: [PATCH 129/293] move placeholder functionality to backend schema --- config/locales/en.yml | 1 + config/locales/js-en.yml | 1 - docs/api/apiv3/tags/schemas.yml | 2 + .../display/display-field.initializer.ts | 4 -- .../fields/display/display-field.module.ts | 7 +++- .../subject-display-field.module.ts | 41 ------------------- .../shared/components/fields/field.base.ts | 1 + .../decorators/property_schema_representer.rb | 8 +++- lib/api/decorators/schema_representer.rb | 12 ++++-- .../schema/work_package_schema_representer.rb | 5 +++ spec/lib/api/v3/support/schema_examples.rb | 8 ++++ .../work_package_schema_representer_spec.rb | 4 ++ 12 files changed, 41 insertions(+), 53 deletions(-) delete mode 100644 frontend/src/app/shared/components/fields/display/field-types/subject-display-field.module.ts diff --git a/config/locales/en.yml b/config/locales/en.yml index e382d9283d7..7e0b9821122 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -4272,6 +4272,7 @@ en: placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: diff --git a/config/locales/js-en.yml b/config/locales/js-en.yml index 0c6de8ff07c..b4fa4cc290d 100644 --- a/config/locales/js-en.yml +++ b/config/locales/js-en.yml @@ -689,7 +689,6 @@ en: placeholders: default: "-" subject: "Enter subject here" - subject_auto_generated: "Automatically generated through type %{type}" selection: "Please select" description: "Description: Click to edit..." relation_description: "Click to add description for this relation" diff --git a/docs/api/apiv3/tags/schemas.yml b/docs/api/apiv3/tags/schemas.yml index ef1de2cb830..ff3cc6c0b52 100644 --- a/docs/api/apiv3/tags/schemas.yml +++ b/docs/api/apiv3/tags/schemas.yml @@ -55,6 +55,7 @@ description: |- | location | If present, contains a reference to the location of the property in the JSON | String | null | | description | If present, contains a formattable, human readable description | Formattable | null | | deprecated | If present, the client should consider the existence of the property deprecated | Boolean | false | + | placeholder | If present, contains the text to display as a placeholder, so if no value is set | String | null | All of the above properties that do not have a default value *must* be present in the schema. For properties that have a default value, the client can assume the default value, if the property is missing. @@ -116,6 +117,7 @@ description: |- "hasDefault": false, "writable": true, "location": "_links", + "placeholder": "Lorem ipsum placeholder", "description": { "format": "markdown", "raw": "A description for field Lorem ipsum. This may contain [links](https://example.com).", diff --git a/frontend/src/app/shared/components/fields/display/display-field.initializer.ts b/frontend/src/app/shared/components/fields/display/display-field.initializer.ts index afdcd65bc76..c33d0b26de1 100644 --- a/frontend/src/app/shared/components/fields/display/display-field.initializer.ts +++ b/frontend/src/app/shared/components/fields/display/display-field.initializer.ts @@ -89,9 +89,6 @@ import { LinkDisplayField } from 'core-app/shared/components/fields/display/fiel import { ProjectPhaseDisplayField, } from 'core-app/shared/components/fields/display/field-types/project-phase-display-field.module'; -import { - SubjectDisplayField, -} from 'core-app/shared/components/fields/display/field-types/subject-display-field.module'; export function initializeCoreDisplayFields(displayFieldService:DisplayFieldService) { return () => { @@ -133,7 +130,6 @@ export function initializeCoreDisplayFields(displayFieldService:DisplayFieldServ .addSpecificFieldType('WorkPackage', WorkPackageIdDisplayField, 'id', ['id']) .addSpecificFieldType('WorkPackage', WorkPackageSpentTimeDisplayField, 'spentTime', ['spentTime']) .addSpecificFieldType('WorkPackage', CombinedDateDisplayField, 'combinedDate', ['combinedDate']) - .addSpecificFieldType('WorkPackage', SubjectDisplayField, 'subject', ['String']) .addSpecificFieldType('TimeEntry', PlainFormattableDisplayField, 'comment', ['comment']) .addSpecificFieldType('Project', ProjectStatusDisplayField, 'status', ['status']) .addSpecificFieldType('TimeEntry', WorkPackageDisplayField, 'work_package', ['workPackage']); diff --git a/frontend/src/app/shared/components/fields/display/display-field.module.ts b/frontend/src/app/shared/components/fields/display/display-field.module.ts index c88024ae202..4c2af09af2e 100644 --- a/frontend/src/app/shared/components/fields/display/display-field.module.ts +++ b/frontend/src/app/shared/components/fields/display/display-field.module.ts @@ -102,7 +102,12 @@ export class DisplayField extends Field { } public get placeholder():string { - return '-'; + // Use the placeholder from the schema if available (set by the backend) + if (this.schema.placeholder) { + return this.schema.placeholder; + } else { + return '-'; + } } public get label() { diff --git a/frontend/src/app/shared/components/fields/display/field-types/subject-display-field.module.ts b/frontend/src/app/shared/components/fields/display/field-types/subject-display-field.module.ts deleted file mode 100644 index 8d003ad1655..00000000000 --- a/frontend/src/app/shared/components/fields/display/field-types/subject-display-field.module.ts +++ /dev/null @@ -1,41 +0,0 @@ -//-- copyright -// OpenProject is an open source project management software. -// Copyright (C) the OpenProject GmbH -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License version 3. -// -// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: -// Copyright (C) 2006-2013 Jean-Philippe Lang -// Copyright (C) 2010-2013 the ChiliProject Team -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -// -// See COPYRIGHT and LICENSE files for more details. -//++ - -import { TextDisplayField } from 'core-app/shared/components/fields/display/field-types/text-display-field.module'; -import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; - -export class SubjectDisplayField extends TextDisplayField { - public get placeholder():string { - // When subject is not writable, it means automatic subject is configured on the type. - if (!this.schema.writable) { - const typeName = (this.resource as WorkPackageResource).type?.name || ''; - return this.I18n.t('js.placeholders.subject_auto_generated', { type: typeName }); - } - return '-'; - } -} diff --git a/frontend/src/app/shared/components/fields/field.base.ts b/frontend/src/app/shared/components/fields/field.base.ts index 2ef73edb703..39293f1c74b 100644 --- a/frontend/src/app/shared/components/fields/field.base.ts +++ b/frontend/src/app/shared/components/fields/field.base.ts @@ -37,6 +37,7 @@ export interface IFieldSchema { hasDefault:boolean; name:string; options?:any; + placeholder?:string; } export class Field extends UntilDestroyedMixin { diff --git a/lib/api/decorators/property_schema_representer.rb b/lib/api/decorators/property_schema_representer.rb index e6928f2106a..27bcc89d467 100644 --- a/lib/api/decorators/property_schema_representer.rb +++ b/lib/api/decorators/property_schema_representer.rb @@ -44,7 +44,8 @@ module API attribute_group: nil, description: nil, current_user: nil, - deprecated: nil + deprecated: nil, + placeholder: nil ) @type = type @name = name @@ -55,6 +56,7 @@ module API @location = derive_location(location) @description = description @deprecated = deprecated + @placeholder = placeholder super(nil, current_user:) end @@ -72,7 +74,8 @@ module API :formula, :location, :description, - :deprecated + :deprecated, + :placeholder property :type, exec_context: :decorator property :name, exec_context: :decorator @@ -86,6 +89,7 @@ module API property :deprecated, exec_context: :decorator property :options, exec_context: :decorator property :formula, exec_context: :decorator, render_nil: false + property :placeholder, exec_context: :decorator, render_nil: false property :location, exec_context: :decorator diff --git a/lib/api/decorators/schema_representer.rb b/lib/api/decorators/schema_representer.rb index f1b116cb902..85c49dfd7a0 100644 --- a/lib/api/decorators/schema_representer.rb +++ b/lib/api/decorators/schema_representer.rb @@ -66,7 +66,8 @@ module API formula: nil, show_if: true, description: nil, - deprecated: nil) + deprecated: nil, + placeholder: nil) getter = ->(*) do schema_property_getter(type, name_source, @@ -81,7 +82,8 @@ module API formula, location, description, - deprecated) + deprecated, + placeholder) end schema_property(property, @@ -304,7 +306,8 @@ module API formula, location, description, - deprecated) + deprecated, + placeholder) name = call_or_translate(name_source) schema = ::API::Decorators::PropertySchemaRepresenter .new(type: call_or_use(type), @@ -315,7 +318,8 @@ module API has_default: call_or_use(has_default), writable: call_or_use(writable), attribute_group: call_or_use(attribute_group), - deprecated:) + deprecated:, + placeholder: call_or_use(placeholder)) schema.min_length = min_length schema.max_length = max_length schema.regular_expression = regular_expression diff --git a/lib/api/v3/work_packages/schema/work_package_schema_representer.rb b/lib/api/v3/work_packages/schema/work_package_schema_representer.rb index 3e8c845ca6e..bef356e401c 100644 --- a/lib/api/v3/work_packages/schema/work_package_schema_representer.rb +++ b/lib/api/v3/work_packages/schema/work_package_schema_representer.rb @@ -117,6 +117,11 @@ module API max_length: 255, has_default: -> { represented.type&.replacement_pattern_defined_for?(:subject) + }, + placeholder: -> { + if represented.type&.replacement_pattern_defined_for?(:subject) + I18n.t("placeholders.templated_hint", type: represented.type.name) + end } schema :description, diff --git a/spec/lib/api/v3/support/schema_examples.rb b/spec/lib/api/v3/support/schema_examples.rb index a1c8ad45d1a..92ef28b213a 100644 --- a/spec/lib/api/v3/support/schema_examples.rb +++ b/spec/lib/api/v3/support/schema_examples.rb @@ -118,6 +118,14 @@ RSpec.shared_examples_for "indicates length requirements" do end end +RSpec.shared_examples_for "defines the placeholder to display" do + it "shows the placeholder value" do + expect(subject) + .to be_json_eql(placeholder.to_json) + .at_path("#{path}/placeholder") + end +end + RSpec.shared_examples_for "links to allowed values directly" do it "has the expected number of links" do expect(subject).to have_json_size(hrefs.size).at_path("#{path}/_links/allowedValues") diff --git a/spec/lib/api/v3/work_packages/schema/work_package_schema_representer_spec.rb b/spec/lib/api/v3/work_packages/schema/work_package_schema_representer_spec.rb index cc61593774d..72aa5639d98 100644 --- a/spec/lib/api/v3/work_packages/schema/work_package_schema_representer_spec.rb +++ b/spec/lib/api/v3/work_packages/schema/work_package_schema_representer_spec.rb @@ -299,6 +299,10 @@ RSpec.describe API::V3::WorkPackages::Schema::WorkPackageSchemaRepresenter do let(:writable) { false } let(:has_default) { true } end + + it_behaves_like "defines the placeholder to display" do + let(:placeholder) { I18n.t("placeholders.templated_hint", type: wp_type.name) } + end end end From 1697cb50b8cc7476fc88f91fc78414505f419f06 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Thu, 5 Feb 2026 13:21:25 -0300 Subject: [PATCH 130/293] Optical adjustment of container query Co-Authored-By: Parimal Satyal <88370597+psatyal@users.noreply.github.com> --- frontend/src/assets/sass/backlogs/_master_backlog.sass | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/frontend/src/assets/sass/backlogs/_master_backlog.sass b/frontend/src/assets/sass/backlogs/_master_backlog.sass index ec90b5c3055..2dd58ba4ed8 100644 --- a/frontend/src/assets/sass/backlogs/_master_backlog.sass +++ b/frontend/src/assets/sass/backlogs/_master_backlog.sass @@ -114,9 +114,11 @@ $op-backlogs-header--points-min-width-narrow: 2rem flex: 1 1 100% overflow: hidden -// Note: Using hardcoded values instead of $breakpoint-sm because -// Sass doesn't interpolate variables in @container query conditions -@container backlogsListsContainer (min-width: 544px) +// Note: Using hardcoded values because Sass doesn't interpolate variables in +// @container query conditions. +// Note: 655px is between $breakpoint-sm and $breakpoint-md. This was found to +// be a sensible value after initial testing with different viewports. +@container backlogsListsContainer (min-width: 655px) .op-backlogs-header-form .FormControl-spacingWrapper flex-direction: row @@ -126,7 +128,7 @@ $op-backlogs-header--points-min-width-narrow: 2rem flex: 1 1 auto min-width: 33% -@container backlogsListsContainer (max-width: 543px) +@container backlogsListsContainer (max-width: 654px) .op-backlogs-header grid-template-columns: 1fr minmax($op-backlogs-header--points-min-width-narrow, max-content) auto From 8767a8aab72bb978c817e0405597d612013dc541 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Thu, 5 Feb 2026 13:26:19 -0300 Subject: [PATCH 131/293] Optical adjustment of story row top margin Co-Authored-By: Parimal Satyal <88370597+psatyal@users.noreply.github.com> --- frontend/src/assets/sass/backlogs/_master_backlog.sass | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/assets/sass/backlogs/_master_backlog.sass b/frontend/src/assets/sass/backlogs/_master_backlog.sass index 2dd58ba4ed8..1a39b92e1e1 100644 --- a/frontend/src/assets/sass/backlogs/_master_backlog.sass +++ b/frontend/src/assets/sass/backlogs/_master_backlog.sass @@ -77,6 +77,7 @@ $op-backlogs-header--points-min-width-narrow: 2rem grid-template-rows: auto auto grid-template-areas: "drag_handle info_line points menu" ". subject subject subject" align-items: center + margin-top: calc(-1 * var(--base-size-4)) margin-bottom: var(--base-size-4) .op-backlogs-story--drag_handle From 86cb8f2daacdf274480559ef4bb2ce28943f8217 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Thu, 5 Feb 2026 14:11:54 -0300 Subject: [PATCH 132/293] Fix collapsible header: make whole area clickable --- .../assets/sass/backlogs/_master_backlog.sass | 1 + .../backlogs/collapsible_component.html.erb | 40 +++++++++---------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/frontend/src/assets/sass/backlogs/_master_backlog.sass b/frontend/src/assets/sass/backlogs/_master_backlog.sass index 1a39b92e1e1..884f9d3cb97 100644 --- a/frontend/src/assets/sass/backlogs/_master_backlog.sass +++ b/frontend/src/assets/sass/backlogs/_master_backlog.sass @@ -51,6 +51,7 @@ $op-backlogs-header--points-min-width-narrow: 2rem align-items: center column-gap: var(--stack-gap-normal) row-gap: var(--base-size-4) + flex: 1 &--title-line display: flex diff --git a/modules/backlogs/app/components/backlogs/collapsible_component.html.erb b/modules/backlogs/app/components/backlogs/collapsible_component.html.erb index 78e27123b5d..efe6818b35a 100644 --- a/modules/backlogs/app/components/backlogs/collapsible_component.html.erb +++ b/modules/backlogs/app/components/backlogs/collapsible_component.html.erb @@ -28,29 +28,29 @@ See COPYRIGHT and LICENSE files for more details. ++# %> <%= render(Primer::BaseComponent.new(**@system_arguments)) do %> - <%= render(Primer::BaseComponent.new(tag: :div, classes: "op-backlogs-collapsible")) do %> + <%= + render( + Primer::BaseComponent.new( + tag: :div, + role: "button", + tabindex: 0, + classes: "op-backlogs-collapsible", + aria: { + label: @toggle_label, + controls: @collapsible_id, + expanded: !@collapsed + }, + data: { + target: "collapsible-header.triggerElement", + action: "click:collapsible-header#toggle keydown:collapsible-header#toggleViaKeyboard" + } + ) + ) do + %> <%= render(Primer::BaseComponent.new(tag: :div, classes: "op-backlogs-collapsible--title-line")) do %> <%= title %> <%= count %> - <%= - render( - Primer::BaseComponent.new( - tag: :div, - role: "button", - tabindex: 0, - classes: "op-backlogs-collapsible--toggle CollapsibleSection--triggerArea", - aria: { - label: @toggle_label, - controls: @collapsible_id, - expanded: !@collapsed - }, - data: { - target: "collapsible-header.triggerElement", - action: "click:collapsible-header#toggle keydown:collapsible-header#toggleViaKeyboard" - } - ) - ) do - %> + <%= render(Primer::BaseComponent.new(tag: :div, classes: "op-backlogs-collapsible--toggle")) do %> <%= render(Primer::Beta::Octicon.new(icon: "chevron-up", hidden: @collapsed, data: { target: "collapsible-header.arrowUp" })) %> <%= render(Primer::Beta::Octicon.new(icon: "chevron-down", hidden: !@collapsed, data: { target: "collapsible-header.arrowDown" })) %> <% end %> From 60e1a62fcf45c85946cfd8ff01b92175126bf1cb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 00:25:09 +0000 Subject: [PATCH 133/293] Fix N+1 queries, improve permission checks - Eager load status and type associations in Story.backlogs to prevent N+1 queries - Use Story.visible scope in RbStoriesController to ensure proper permission checks Co-authored-by: myabc <755+myabc@users.noreply.github.com> --- modules/backlogs/app/controllers/rb_stories_controller.rb | 4 ++-- modules/backlogs/app/models/story.rb | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/backlogs/app/controllers/rb_stories_controller.rb b/modules/backlogs/app/controllers/rb_stories_controller.rb index 486e4eb24e5..41803ba9249 100644 --- a/modules/backlogs/app/controllers/rb_stories_controller.rb +++ b/modules/backlogs/app/controllers/rb_stories_controller.rb @@ -32,7 +32,7 @@ class RbStoriesController < RbApplicationController include OpTurbo::ComponentStream def move - story = Story.find(params[:id]) + story = Story.visible.find(params[:id]) call = Stories::UpdateService .new(user: current_user, story:) @@ -62,7 +62,7 @@ class RbStoriesController < RbApplicationController end def reorder - story = Story.find(params[:id]) + story = Story.visible.find(params[:id]) call = Stories::UpdateService .new(user: current_user, story:) diff --git a/modules/backlogs/app/models/story.rb b/modules/backlogs/app/models/story.rb index d72ae19edf9..4b0d738c614 100644 --- a/modules/backlogs/app/models/story.rb +++ b/modules/backlogs/app/models/story.rb @@ -33,7 +33,9 @@ class Story < WorkPackage options.reverse_merge!(order: Story::ORDER, conditions: Story.condition(project_id, sprint_ids)) - candidates = Story.where(options[:conditions]).order(Arel.sql(options[:order])) + candidates = Story.where(options[:conditions]) + .includes(:status, :type) + .order(Arel.sql(options[:order])) stories_by_version = Hash.new do |hash, sprint_id| hash[sprint_id] = [] From caebb8c7f03f6e8c8439b453387532a16390db9b Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Thu, 5 Feb 2026 14:56:28 -0300 Subject: [PATCH 134/293] Fix burndown chart responsiveness Wrap the chart canvas in a positioned container with defined height so ChartJS can properly resize on window resize. Co-Authored-By: Claude Opus 4.5 --- .../features/backlogs/burndown-chart.component.html | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/features/backlogs/burndown-chart.component.html b/frontend/src/app/features/backlogs/burndown-chart.component.html index 7a1b592ddee..07c46743f62 100644 --- a/frontend/src/app/features/backlogs/burndown-chart.component.html +++ b/frontend/src/app/features/backlogs/burndown-chart.component.html @@ -1,7 +1,9 @@ - +
    + +

    From 822784bf4c4f58087e2be033160ce6d2fd7583c3 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Thu, 5 Feb 2026 14:50:38 -0300 Subject: [PATCH 135/293] Hide burndown chart debug info in production The debug section showing raw chart data is now only rendered when not in production mode. Co-Authored-By: Claude Opus 4.5 --- .../backlogs/burndown-chart.component.html | 18 ++++++++++-------- .../backlogs/burndown-chart.component.ts | 2 ++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/frontend/src/app/features/backlogs/burndown-chart.component.html b/frontend/src/app/features/backlogs/burndown-chart.component.html index 07c46743f62..fb72c70a9ac 100644 --- a/frontend/src/app/features/backlogs/burndown-chart.component.html +++ b/frontend/src/app/features/backlogs/burndown-chart.component.html @@ -5,12 +5,14 @@ [type]="'line'">
    -
    +@if (isDevMode) { +
    -
    - Debug - -
    {{maxValue() }}
    -
    {{lineChartData() | json}}
    -
    -
    +
    + Debug + +
    {{maxValue() }}
    +
    {{lineChartData() | json}}
    +
    +
    +} diff --git a/frontend/src/app/features/backlogs/burndown-chart.component.ts b/frontend/src/app/features/backlogs/burndown-chart.component.ts index a07ae8b41e5..7989f667c4c 100644 --- a/frontend/src/app/features/backlogs/burndown-chart.component.ts +++ b/frontend/src/app/features/backlogs/burndown-chart.component.ts @@ -32,6 +32,7 @@ import { ChartData, ChartOptions } from 'chart.js'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import PrimerColorsPlugin from 'core-app/shared/components/work-package-graphs/plugin.primer-colors'; import { BaseChartDirective, provideCharts, withDefaultRegisterables } from 'ng2-charts'; +import { environment } from '../../../environments/environment'; const BURNDOWN_Y_SCALE_MIN = 25; @@ -43,6 +44,7 @@ const BURNDOWN_Y_SCALE_MIN = 25; changeDetection: ChangeDetectionStrategy.OnPush }) export class BurndownChartComponent { + readonly isDevMode = !environment.production; readonly i18n = inject(I18nService); readonly chartData = input.required(); From 9c3be80ae0bc550205ca81d1f7fdced04362fb53 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Thu, 5 Feb 2026 16:04:51 -0300 Subject: [PATCH 136/293] Fix context menu feature spec description Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- modules/backlogs/spec/features/backlogs/context_menu_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/backlogs/spec/features/backlogs/context_menu_spec.rb b/modules/backlogs/spec/features/backlogs/context_menu_spec.rb index f88bec9c30c..540699b5aea 100644 --- a/modules/backlogs/spec/features/backlogs/context_menu_spec.rb +++ b/modules/backlogs/spec/features/backlogs/context_menu_spec.rb @@ -98,7 +98,7 @@ RSpec.describe "Backlogs context menu", :js do display: VersionSetting::DISPLAY_RIGHT) end - it "only displays 4 menu entries" do + it "only displays 2 menu entries" do within_backlog_context_menu do |menu| expect(menu).to have_selector :menuitem, count: 2 expect(menu).to have_selector :menuitem, "New story" From c454b6c89bcec6420205c671010f321da4e63e05 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Thu, 5 Feb 2026 20:02:51 +0200 Subject: [PATCH 137/293] Fix error of nil positions when calculating max_position --- modules/backlogs/app/components/backlogs/backlog_component.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/backlogs/app/components/backlogs/backlog_component.rb b/modules/backlogs/app/components/backlogs/backlog_component.rb index 2b72005c4fc..a25366262a3 100644 --- a/modules/backlogs/app/components/backlogs/backlog_component.rb +++ b/modules/backlogs/app/components/backlogs/backlog_component.rb @@ -66,7 +66,7 @@ module Backlogs end def max_position - stories.map(&:position).max + stories.filter_map(&:position).max end def drop_target_config From df2b48b938e25f67fbedbebccce47af4bc3626b8 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Thu, 5 Feb 2026 21:35:20 +0200 Subject: [PATCH 138/293] Update the move_after method to handle both the old style prev_id and new position drag and drop args --- .../app/controllers/rb_stories_controller.rb | 13 ++++++---- .../app/controllers/rb_tasks_controller.rb | 4 +-- .../app/services/stories/update_service.rb | 6 ++--- .../app/services/tasks/create_service.rb | 4 +-- .../app/services/tasks/update_service.rb | 4 +-- .../lib/open_project/backlogs/list.rb | 26 +++++++++---------- 6 files changed, 30 insertions(+), 27 deletions(-) diff --git a/modules/backlogs/app/controllers/rb_stories_controller.rb b/modules/backlogs/app/controllers/rb_stories_controller.rb index 41803ba9249..7a08893eae7 100644 --- a/modules/backlogs/app/controllers/rb_stories_controller.rb +++ b/modules/backlogs/app/controllers/rb_stories_controller.rb @@ -33,13 +33,16 @@ class RbStoriesController < RbApplicationController def move story = Story.visible.find(params[:id]) + # The #move_after called in update service required reloading the story, hence + # it is required to memoize the previous version_id. + version_id_was = story.version_id call = Stories::UpdateService .new(user: current_user, story:) - .call(attributes: { - version_id: move_params[:target_id], - position: move_params[:position] - }) + .call( + attributes: { version_id: move_params[:target_id] }, + position: move_params[:position].to_i + ) unless call.success? render_error_flash_message_via_turbo_stream(message: I18n.t(:notice_unsuccessful_update)) # TODO: display reason @@ -48,7 +51,7 @@ class RbStoriesController < RbApplicationController backlog = Backlog.for(sprint: @sprint, project: @project) replace_via_turbo_stream(component: Backlogs::BacklogComponent.new(backlog:, project: @project)) - if story.saved_change_to_version_id? + if story.version_id != version_id_was new_sprint = story.version.becomes(Sprint) new_backlog = Backlog.for(sprint: new_sprint, project: @project) diff --git a/modules/backlogs/app/controllers/rb_tasks_controller.rb b/modules/backlogs/app/controllers/rb_tasks_controller.rb index d1bd84f1559..bab78c34bb5 100644 --- a/modules/backlogs/app/controllers/rb_tasks_controller.rb +++ b/modules/backlogs/app/controllers/rb_tasks_controller.rb @@ -36,7 +36,7 @@ class RbTasksController < RbApplicationController def create call = ::Tasks::CreateService .new(user: current_user) - .call(attributes: task_params.merge(project: @project), prev: params[:prev]) + .call(attributes: task_params.merge(project: @project), prev_id: params[:prev]) respond_with_task call end @@ -46,7 +46,7 @@ class RbTasksController < RbApplicationController call = ::Tasks::UpdateService .new(user: current_user, task:) - .call(attributes: task_params, prev: params[:prev]) + .call(attributes: task_params, prev_id: params[:prev]) respond_with_task call end diff --git a/modules/backlogs/app/services/stories/update_service.rb b/modules/backlogs/app/services/stories/update_service.rb index fb835a7dbcc..fd5f94f50d6 100644 --- a/modules/backlogs/app/services/stories/update_service.rb +++ b/modules/backlogs/app/services/stories/update_service.rb @@ -34,14 +34,14 @@ class Stories::UpdateService self.story = story end - def call(attributes: {}, prev: nil) + def call(attributes: {}, position: nil) create_call = WorkPackages::UpdateService .new(user:, model: story) .call(**attributes.to_h.symbolize_keys) - if create_call.success? - create_call.result.move_after prev + if create_call.success? && position + create_call.result.move_after(position:) end create_call diff --git a/modules/backlogs/app/services/tasks/create_service.rb b/modules/backlogs/app/services/tasks/create_service.rb index 47591c790f0..fd34ec2bb97 100644 --- a/modules/backlogs/app/services/tasks/create_service.rb +++ b/modules/backlogs/app/services/tasks/create_service.rb @@ -33,7 +33,7 @@ class Tasks::CreateService self.user = user end - def call(attributes: {}, prev: "") + def call(attributes: {}, prev_id: "") attributes[:type_id] = Task.type create_call = WorkPackages::CreateService @@ -41,7 +41,7 @@ class Tasks::CreateService .call(**attributes) if create_call.success? - create_call.result.move_after prev + create_call.result.move_after prev_id: end create_call diff --git a/modules/backlogs/app/services/tasks/update_service.rb b/modules/backlogs/app/services/tasks/update_service.rb index b936db0344f..a249626d024 100644 --- a/modules/backlogs/app/services/tasks/update_service.rb +++ b/modules/backlogs/app/services/tasks/update_service.rb @@ -34,14 +34,14 @@ class Tasks::UpdateService self.task = task end - def call(attributes: {}, prev: "") + def call(attributes: {}, prev_id: "") create_call = WorkPackages::UpdateService .new(user:, model: task) .call(**attributes) if create_call.success? - create_call.result.move_after prev + create_call.result.move_after prev_id: end create_call diff --git a/modules/backlogs/lib/open_project/backlogs/list.rb b/modules/backlogs/lib/open_project/backlogs/list.rb index 0a593a33b80..67aef13fe8d 100644 --- a/modules/backlogs/lib/open_project/backlogs/list.rb +++ b/modules/backlogs/lib/open_project/backlogs/list.rb @@ -53,28 +53,28 @@ module OpenProject::Backlogs::List end module InstanceMethods - def move_after(prev_id) + def move_after(position: nil, prev_id: nil) + if acts_as_list_list.none?(:position) + # If no items have a position, create an order on position + # silently. This can happen when sorting inside a version for the first + # time after backlogs was activated and there have already been items + # inside the version at the time of backlogs activation + set_default_prev_positions_silently(acts_as_list_list.last) + end + # Remove so the potential 'prev' has a correct position remove_from_list reload + id_or_position = position ? { position: position - 1 } : { id: prev_id } - prev = self.class.find_by(id: prev_id.to_i) + prev = acts_as_list_list.find_by(**id_or_position) - # If it should be the first story, move it to the 1st position if prev.blank? + # If it should be the first story, move it to the 1st position insert_at move_to_top - - # If its predecessor has no position, create an order on position - # silently. This can happen when sorting inside a version for the first - # time after backlogs was activated and there have already been items - # inside the version at the time of backlogs activation - elsif !prev.in_list? - prev_pos = set_default_prev_positions_silently(prev) - insert_at(prev_pos += 1) - - # There's a valid predecessor else + # There's a valid predecessor insert_at(prev.position + 1) end end From 2382e371077a0c95ad33063b58bbbe0ee41357c5 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Thu, 5 Feb 2026 16:09:47 -0300 Subject: [PATCH 139/293] Hide divider in story menu when only one item When a backlog has only a single story, the move menu items are not shown. This change also hides the divider in that case to avoid an orphan separator at the bottom of the menu. Co-Authored-By: Claude Opus 4.5 --- .../app/components/backlogs/story_menu_component.html.erb | 7 ++++--- .../app/components/backlogs/story_menu_component.rb | 4 ++++ .../spec/components/backlogs/story_menu_component_spec.rb | 6 ++++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/modules/backlogs/app/components/backlogs/story_menu_component.html.erb b/modules/backlogs/app/components/backlogs/story_menu_component.html.erb index 424bb34efbd..617e7085733 100644 --- a/modules/backlogs/app/components/backlogs/story_menu_component.html.erb +++ b/modules/backlogs/app/components/backlogs/story_menu_component.html.erb @@ -54,8 +54,9 @@ See COPYRIGHT and LICENSE files for more details. item.with_leading_visual_icon(icon: :"screen-full") end - menu.with_divider - - build_move_menu(menu) + if show_move_items? + menu.with_divider + build_move_menu(menu) + end end %> diff --git a/modules/backlogs/app/components/backlogs/story_menu_component.rb b/modules/backlogs/app/components/backlogs/story_menu_component.rb index cfd474da13e..99581a19047 100644 --- a/modules/backlogs/app/components/backlogs/story_menu_component.rb +++ b/modules/backlogs/app/components/backlogs/story_menu_component.rb @@ -44,6 +44,10 @@ module Backlogs private + def show_move_items? + !(first_item? && last_item?) + end + def build_move_menu(menu) unless first_item? build_move_item(menu, label: I18n.t(:label_sort_highest), direction: "highest", icon: :"move-to-top") diff --git a/modules/backlogs/spec/components/backlogs/story_menu_component_spec.rb b/modules/backlogs/spec/components/backlogs/story_menu_component_spec.rb index 99758863c03..b9ac8feaac4 100644 --- a/modules/backlogs/spec/components/backlogs/story_menu_component_spec.rb +++ b/modules/backlogs/spec/components/backlogs/story_menu_component_spec.rb @@ -170,6 +170,12 @@ RSpec.describe Backlogs::StoryMenuComponent, type: :component do expect(page).to have_no_text(I18n.t(:label_sort_lower)) expect(page).to have_no_text(I18n.t(:label_sort_lowest)) end + + it "hides the divider" do + render_component(position: 1, max_position: 1) + + expect(page).to have_no_css(".ActionList-sectionDivider") + end end end end From adc5b1b24a8d70777c351bfb5d1227486a31b79e Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Thu, 5 Feb 2026 16:36:51 -0300 Subject: [PATCH 140/293] Improve Backlogs "not configured" page Replace the plain text message with a proper PageHeader and Blankslate component when Backlogs is not configured. This provides a consistent UI with the rest of the application and a clear call-to-action to configure the module. Co-Authored-By: Claude Opus 4.5 --- .../app/views/shared/not_configured.html.erb | 38 ++++++++++++++----- modules/backlogs/config/locales/en.yml | 4 ++ 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/modules/backlogs/app/views/shared/not_configured.html.erb b/modules/backlogs/app/views/shared/not_configured.html.erb index 098b7924ebe..c1475db3916 100644 --- a/modules/backlogs/app/views/shared/not_configured.html.erb +++ b/modules/backlogs/app/views/shared/not_configured.html.erb @@ -1,4 +1,4 @@ -<%#-- copyright +<%# -- copyright OpenProject is an open source project management software. Copyright (C) the OpenProject GmbH @@ -25,13 +25,31 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. See COPYRIGHT and LICENSE files for more details. -++#%> +++# %> -
    - <%= t( - :label_backlogs_unconfigured, - administration: t(:label_administration), - plugins: t(:label_plugins), - configure: t(:button_configure) - ) %> -
    +<% html_title t(:label_backlogs) %> + +<% content_for :content_header do %> + <%= + render Primer::OpenProject::PageHeader.new do |header| + header.with_title { t(:label_backlogs) } + header.with_breadcrumbs( + [{ href: project_overview_path(@project.identifier), text: @project.name }, + t(:label_backlogs)] + ) + end + %> +<% end %> + +<% content_for :content_body do %> + <%= + render(Primer::Beta::Blankslate.new(border: true, spacious: true)) do |blankslate| + blankslate.with_visual_icon(icon: :"op-backlogs") + blankslate.with_heading(tag: :h2).with_content(t(:backlogs_not_configured_title)) + blankslate.with_description_content(t(:backlogs_not_configured_description)) + blankslate.with_secondary_action(href: admin_backlogs_settings_path, scheme: :default) do + t(:backlogs_not_configured_action_text) + end + end + %> +<% end %> diff --git a/modules/backlogs/config/locales/en.yml b/modules/backlogs/config/locales/en.yml index 002b73f14c2..dbeee1707a2 100644 --- a/modules/backlogs/config/locales/en.yml +++ b/modules/backlogs/config/locales/en.yml @@ -123,6 +123,10 @@ en: backlogs_empty_title: "No versions are defined yet" backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: story_points: "Story points" story_points_ideal: "Story points (ideal)" From e45313279549d8cfb175048bb6a317d81067f82c Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Thu, 5 Feb 2026 16:53:40 -0300 Subject: [PATCH 141/293] Remove unused RbCommonHelper#all_workflows method Co-Authored-By: Claude Opus 4.5 --- modules/backlogs/app/helpers/rb_common_helper.rb | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/modules/backlogs/app/helpers/rb_common_helper.rb b/modules/backlogs/app/helpers/rb_common_helper.rb index c59c635c974..d93c81c7398 100644 --- a/modules/backlogs/app/helpers/rb_common_helper.rb +++ b/modules/backlogs/app/helpers/rb_common_helper.rb @@ -142,18 +142,6 @@ module RbCommonHelper @all_work_package_status_by_id[id] end - # Returns all distinct virtual workflows for the roles the current user has in the project and the story types. - # Virtual workflow because not every instance of a workflow in the database will be returned but a representation - # distinct by type_id, old_status_id and new_status_id. This helps in case a lot of workflows are configured. - def all_workflows - Workflow - .includes(%i[new_status old_status]) - .where(role_id: User.current.roles_for_project(@project).map(&:id), - type_id: story_types.map(&:id)) - .group(:type_id, :old_status_id, :new_status_id) - .reselect(:type_id, :old_status_id, :new_status_id) - end - def all_work_package_status @all_work_package_status ||= Status.order(Arel.sql("position ASC")) end From b106a786dbaf7537defad693f24f581ecb1087a4 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Thu, 5 Feb 2026 16:57:46 -0300 Subject: [PATCH 142/293] Autocorrect Rubocop offenses in controllers --- .../backlogs/app/controllers/backlogs_settings_controller.rb | 2 ++ .../app/controllers/projects/settings/backlogs_controller.rb | 4 +++- modules/backlogs/app/controllers/rb_application_controller.rb | 2 ++ .../backlogs/app/controllers/rb_burndown_charts_controller.rb | 2 ++ modules/backlogs/app/controllers/rb_impediments_controller.rb | 2 ++ modules/backlogs/app/controllers/rb_queries_controller.rb | 2 ++ modules/backlogs/app/controllers/rb_taskboards_controller.rb | 2 ++ modules/backlogs/app/controllers/rb_tasks_controller.rb | 4 +++- modules/backlogs/app/controllers/rb_wikis_controller.rb | 2 ++ 9 files changed, 20 insertions(+), 2 deletions(-) diff --git a/modules/backlogs/app/controllers/backlogs_settings_controller.rb b/modules/backlogs/app/controllers/backlogs_settings_controller.rb index 09318d09c68..845c60b6b7f 100644 --- a/modules/backlogs/app/controllers/backlogs_settings_controller.rb +++ b/modules/backlogs/app/controllers/backlogs_settings_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH diff --git a/modules/backlogs/app/controllers/projects/settings/backlogs_controller.rb b/modules/backlogs/app/controllers/projects/settings/backlogs_controller.rb index 6fa3587d0fc..b53f8be524b 100644 --- a/modules/backlogs/app/controllers/projects/settings/backlogs_controller.rb +++ b/modules/backlogs/app/controllers/projects/settings/backlogs_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH @@ -30,7 +32,7 @@ class Projects::Settings::BacklogsController < Projects::SettingsController menu_item :settings_backlogs def show - @statuses_done_for_project = @project.done_statuses.select(:id).map(&:id) + @statuses_done_for_project = @project.done_statuses.pluck(:id) end def update diff --git a/modules/backlogs/app/controllers/rb_application_controller.rb b/modules/backlogs/app/controllers/rb_application_controller.rb index b756ae04b64..944ce7c2452 100644 --- a/modules/backlogs/app/controllers/rb_application_controller.rb +++ b/modules/backlogs/app/controllers/rb_application_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH diff --git a/modules/backlogs/app/controllers/rb_burndown_charts_controller.rb b/modules/backlogs/app/controllers/rb_burndown_charts_controller.rb index de32109c5d4..914d1f0a214 100644 --- a/modules/backlogs/app/controllers/rb_burndown_charts_controller.rb +++ b/modules/backlogs/app/controllers/rb_burndown_charts_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH diff --git a/modules/backlogs/app/controllers/rb_impediments_controller.rb b/modules/backlogs/app/controllers/rb_impediments_controller.rb index d67c86b22dc..1a95316f107 100644 --- a/modules/backlogs/app/controllers/rb_impediments_controller.rb +++ b/modules/backlogs/app/controllers/rb_impediments_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH diff --git a/modules/backlogs/app/controllers/rb_queries_controller.rb b/modules/backlogs/app/controllers/rb_queries_controller.rb index da11a815503..eaf990c0fab 100644 --- a/modules/backlogs/app/controllers/rb_queries_controller.rb +++ b/modules/backlogs/app/controllers/rb_queries_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH diff --git a/modules/backlogs/app/controllers/rb_taskboards_controller.rb b/modules/backlogs/app/controllers/rb_taskboards_controller.rb index 73cda7f7b90..05196ec200e 100644 --- a/modules/backlogs/app/controllers/rb_taskboards_controller.rb +++ b/modules/backlogs/app/controllers/rb_taskboards_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH diff --git a/modules/backlogs/app/controllers/rb_tasks_controller.rb b/modules/backlogs/app/controllers/rb_tasks_controller.rb index bab78c34bb5..5182715d05f 100644 --- a/modules/backlogs/app/controllers/rb_tasks_controller.rb +++ b/modules/backlogs/app/controllers/rb_tasks_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH @@ -31,7 +33,7 @@ class RbTasksController < RbApplicationController # attributes. This is necessary for now as we still directly use `attributes=` # in non-controller code. PERMITTED_PARAMS = ["id", "subject", "assigned_to_id", "remaining_hours", "parent_id", - "estimated_hours", "status_id", "sprint_id"] + "estimated_hours", "status_id", "sprint_id"].freeze def create call = ::Tasks::CreateService diff --git a/modules/backlogs/app/controllers/rb_wikis_controller.rb b/modules/backlogs/app/controllers/rb_wikis_controller.rb index 4de3f05da69..edd31eef7c1 100644 --- a/modules/backlogs/app/controllers/rb_wikis_controller.rb +++ b/modules/backlogs/app/controllers/rb_wikis_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH From 4351c539fa91876ff7094329ad7beadcfc148063 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Thu, 5 Feb 2026 16:59:20 -0300 Subject: [PATCH 143/293] Disable Metrics/AbcSize for RbStoriesController#move --- modules/backlogs/app/controllers/rb_stories_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/backlogs/app/controllers/rb_stories_controller.rb b/modules/backlogs/app/controllers/rb_stories_controller.rb index 7a08893eae7..b0a6c3db1a6 100644 --- a/modules/backlogs/app/controllers/rb_stories_controller.rb +++ b/modules/backlogs/app/controllers/rb_stories_controller.rb @@ -31,7 +31,7 @@ class RbStoriesController < RbApplicationController include OpTurbo::ComponentStream - def move + def move # rubocop:disable Metrics/AbcSize story = Story.visible.find(params[:id]) # The #move_after called in update service required reloading the story, hence # it is required to memoize the previous version_id. From 654d2564624e6912d4cf40e63c67adaaaeef3439 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Thu, 5 Feb 2026 17:12:42 -0300 Subject: [PATCH 144/293] Disable Metrics/AbcSize for Story.backlogs --- modules/backlogs/app/models/story.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/backlogs/app/models/story.rb b/modules/backlogs/app/models/story.rb index 4b0d738c614..6948b8dfdbc 100644 --- a/modules/backlogs/app/models/story.rb +++ b/modules/backlogs/app/models/story.rb @@ -29,7 +29,7 @@ class Story < WorkPackage extend OpenProject::Backlogs::Mixins::PreventIssueSti - def self.backlogs(project_id, sprint_ids, options = {}) + def self.backlogs(project_id, sprint_ids, options = {}) # rubocop:disable Metrics/AbcSize options.reverse_merge!(order: Story::ORDER, conditions: Story.condition(project_id, sprint_ids)) From 4e104c4656f00c4f4ea37dfb6945956fb302bc94 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Thu, 5 Feb 2026 22:40:15 +0200 Subject: [PATCH 145/293] Fix Stories::CreateService prev argument --- modules/backlogs/app/services/stories/create_service.rb | 4 ++-- modules/backlogs/lib/open_project/backlogs/list.rb | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/backlogs/app/services/stories/create_service.rb b/modules/backlogs/app/services/stories/create_service.rb index f15a00de3f2..d8c523914b9 100644 --- a/modules/backlogs/app/services/stories/create_service.rb +++ b/modules/backlogs/app/services/stories/create_service.rb @@ -33,13 +33,13 @@ class Stories::CreateService self.user = user end - def call(attributes: {}, prev: nil) + def call(attributes: {}, position: nil) create_call = WorkPackages::CreateService .new(user:) .call(**attributes.symbolize_keys) if create_call.success? - create_call.result.move_after prev + create_call.result.move_after position: end create_call diff --git a/modules/backlogs/lib/open_project/backlogs/list.rb b/modules/backlogs/lib/open_project/backlogs/list.rb index 67aef13fe8d..e884a450b5e 100644 --- a/modules/backlogs/lib/open_project/backlogs/list.rb +++ b/modules/backlogs/lib/open_project/backlogs/list.rb @@ -148,6 +148,8 @@ module OpenProject::Backlogs::List end def set_default_prev_positions_silently(prev) + return if prev.nil? + if prev.is_task? prev.version.rebuild_task_positions(prev) else From 81d5cca2aa4d1c7f7d9e4275ad3d2445d40860e9 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Thu, 5 Feb 2026 22:52:29 +0200 Subject: [PATCH 146/293] Fix not configured spec --- modules/backlogs/spec/views/shared/not_configured_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/backlogs/spec/views/shared/not_configured_spec.rb b/modules/backlogs/spec/views/shared/not_configured_spec.rb index f3fa729b26d..57d240e138f 100644 --- a/modules/backlogs/spec/views/shared/not_configured_spec.rb +++ b/modules/backlogs/spec/views/shared/not_configured_spec.rb @@ -29,6 +29,8 @@ require "spec_helper" RSpec.describe "shared/not_configured" do + before { assign(:project, create(:project)) } + it "renders without errors" do render end From 9fb06a8ab84eac1266356fad61e4b5c2b82a3b90 Mon Sep 17 00:00:00 2001 From: OpenProject Actions CI Date: Fri, 6 Feb 2026 03:51:00 +0000 Subject: [PATCH 147/293] update locales from crowdin [ci skip] --- config/locales/crowdin/fr.yml | 44 +- config/locales/crowdin/it.yml | 170 +- config/locales/crowdin/js-it.yml | 6 +- config/locales/crowdin/js-ko.yml | 6 +- config/locales/crowdin/js-pl.yml | 10 +- config/locales/crowdin/js-tr.yml | 6 +- config/locales/crowdin/js-vi.yml | 1151 ++-- config/locales/crowdin/js-zh-CN.yml | 6 +- config/locales/crowdin/ko.yml | 168 +- config/locales/crowdin/pl.yml | 188 +- config/locales/crowdin/uk.yml | 26 +- config/locales/crowdin/vi.seeders.yml | 404 +- config/locales/crowdin/vi.yml | 4718 ++++++++--------- config/locales/crowdin/zh-CN.yml | 142 +- .../auth_saml/config/locales/crowdin/tr.yml | 16 +- .../auth_saml/config/locales/crowdin/vi.yml | 206 +- .../avatars/config/locales/crowdin/js-vi.yml | 9 +- modules/avatars/config/locales/crowdin/vi.yml | 6 +- .../backlogs/config/locales/crowdin/js-vi.yml | 2 +- .../backlogs/config/locales/crowdin/pl.yml | 16 +- .../backlogs/config/locales/crowdin/vi.yml | 116 +- modules/bim/config/locales/crowdin/js-vi.yml | 16 +- .../bim/config/locales/crowdin/vi.seeders.yml | 496 +- modules/bim/config/locales/crowdin/vi.yml | 60 +- .../boards/config/locales/crowdin/js-vi.yml | 54 +- modules/boards/config/locales/crowdin/vi.yml | 28 +- modules/budgets/config/locales/crowdin/vi.yml | 52 +- .../calendar/config/locales/crowdin/js-vi.yml | 6 +- .../calendar/config/locales/crowdin/vi.yml | 8 +- .../costs/config/locales/crowdin/js-vi.yml | 8 +- modules/costs/config/locales/crowdin/vi.yml | 246 +- .../documents/config/locales/crowdin/it.yml | 2 +- .../documents/config/locales/crowdin/pl.yml | 2 +- .../config/locales/crowdin/vi.seeders.yml | 12 +- .../documents/config/locales/crowdin/vi.yml | 162 +- .../config/locales/crowdin/zh-CN.yml | 4 +- .../gantt/config/locales/crowdin/js-vi.yml | 4 +- modules/gantt/config/locales/crowdin/vi.yml | 2 +- .../config/locales/crowdin/js-vi.yml | 28 +- .../config/locales/crowdin/vi.yml | 50 +- .../config/locales/crowdin/js-vi.yml | 26 +- .../config/locales/crowdin/vi.yml | 28 +- .../grids/config/locales/crowdin/js-pl.yml | 2 +- .../grids/config/locales/crowdin/js-vi.yml | 48 +- modules/grids/config/locales/crowdin/vi.yml | 24 +- .../job_status/config/locales/crowdin/vi.yml | 16 +- .../ldap_groups/config/locales/crowdin/vi.yml | 56 +- modules/meeting/config/locales/crowdin/fr.yml | 30 +- modules/meeting/config/locales/crowdin/it.yml | 34 +- .../meeting/config/locales/crowdin/js-vi.yml | 4 +- modules/meeting/config/locales/crowdin/ko.yml | 12 +- modules/meeting/config/locales/crowdin/pl.yml | 34 +- modules/meeting/config/locales/crowdin/uk.yml | 6 +- .../config/locales/crowdin/vi.seeders.yml | 12 +- modules/meeting/config/locales/crowdin/vi.yml | 758 +-- .../meeting/config/locales/crowdin/zh-CN.yml | 34 +- .../config/locales/crowdin/vi.yml | 212 +- .../config/locales/crowdin/js-vi.yml | 2 +- .../overviews/config/locales/crowdin/vi.yml | 6 +- .../recaptcha/config/locales/crowdin/vi.yml | 12 +- .../config/locales/crowdin/js-vi.yml | 2 +- .../reporting/config/locales/crowdin/vi.yml | 102 +- .../storages/config/locales/crowdin/fr.yml | 22 +- .../storages/config/locales/crowdin/it.yml | 22 +- .../storages/config/locales/crowdin/js-vi.yml | 84 +- .../storages/config/locales/crowdin/ko.yml | 22 +- .../storages/config/locales/crowdin/pl.yml | 22 +- .../storages/config/locales/crowdin/uk.yml | 2 +- .../storages/config/locales/crowdin/vi.yml | 663 +-- .../storages/config/locales/crowdin/zh-CN.yml | 22 +- .../config/locales/crowdin/js-vi.yml | 26 +- .../config/locales/crowdin/vi.yml | 22 +- .../config/locales/crowdin/js-vi.yml | 2 +- .../config/locales/crowdin/vi.yml | 134 +- .../webhooks/config/locales/crowdin/vi.yml | 68 +- .../xls_export/config/locales/crowdin/vi.yml | 8 +- 76 files changed, 5618 insertions(+), 5617 deletions(-) diff --git a/config/locales/crowdin/fr.yml b/config/locales/crowdin/fr.yml index bce2ab76a1e..e97c1c2d0e6 100644 --- a/config/locales/crowdin/fr.yml +++ b/config/locales/crowdin/fr.yml @@ -111,21 +111,21 @@ fr: link: "webhook" mcp_configurations: index: - description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." - resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." - resources_submit: "Update resources" - tools_heading: "Tools" - tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." - tools_submit: "Update tools" + description: "Le protocole de contexte de modèle permet aux agents d'intelligence artificielle de fournir à leurs utilisateurs les outils et les ressources exposés par cette instance d'OpenProject." + resources_heading: "Ressources" + resources_description: "OpenProject implémente les outils suivants. Chacun d'entre eux peut être activé, renommé et décrit comme vous le souhaitez. Pour en savoir plus, veuillez vous référer à la [documentation sur les ressources MCP](docs_url)." + resources_submit: "Mettre à jour les ressources" + tools_heading: "Outils" + tools_description: "OpenProject implémente les outils suivants. Chacun d'entre eux peut être activé, renommé et décrit comme vous le souhaitez. Pour en savoir plus, veuillez vous référer à la [documentation sur les outils MCP](docs_url)." + tools_submit: "Mettre à jour les outils" multi_update: - success: "MCP configurations were updated successfully." + success: "Les configurations MCP ont été mises à jour avec succès." server_form: - description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + description_caption: "Comment le serveur MCP sera décrit aux autres applications qui s'y connectent." + title_caption: "Un titre court affiché aux applications qui se connectent au serveur MCP." update: - failure: "MCP configuration could not be updated." - success: "MCP configuration was updated successfully." + failure: "La configuration MCP n'a pas pu être mise à jour." + success: "La configuration MCP a été mise à jour avec succès." scim_clients: authentication_methods: sso: "JWT du fournisseur d'identité" @@ -1252,8 +1252,8 @@ fr: port: "Port" tls_certificate_string: "Certificat SSL du serveur LDAP" mcp_configuration: - enabled: Enabled - title: Title + enabled: Activé + title: Titre description: Description member: roles: "Rôles" @@ -2462,12 +2462,12 @@ fr: edit_attribute_groups: Modifier les groupes d'attributs gantt_pdf_export: Exportation PDF de diagramme de Gantt ldap_groups: Synchronisation des utilisateurs LDAP et des groupes - mcp_server: MCP Server + mcp_server: Serveur MCP nextcloud_sso: Authentification unique pour le stockage Nextcloud one_drive_sharepoint_file_storage: Stockage de fichiers OneDrive/SharePoint placeholder_users: Utilisateurs fictifs portfolio_management: Gestion du portefeuille - project_creation_wizard: Project initiation request + project_creation_wizard: Demande de lancement du projet project_list_sharing: Partage de liste de projets readonly_work_packages: Lots de travaux en lecture seule scim_api: API du serveur SCIM @@ -2540,7 +2540,7 @@ fr: title: "Actions personnalisées" description: "Les actions personnalisées sont des raccourcis en un clic vers un ensemble d'actions prédéfinies que vous pouvez rendre disponibles sur certains lots de travaux en fonction de l'état, du rôle, du type ou du projet." mcp_server: - description: "Integrate AI agents with your OpenProject instance through MCP." + description: "Intégrez des agents d'intelligence artificielle à votre instance OpenProject grâce à MCP." nextcloud_sso: title: "Authentification unique pour le stockage Nextcloud" description: "Activez l'authentification transparente et sécurisée pour votre stockage Nextcloud avec l'authentification unique. Simplifiez la gestion des accès et augmentez la commodité des utilisateurs." @@ -2553,7 +2553,7 @@ fr: virus_scanning: description: "Assurez-vous que les fichiers téléversés dans OpenProject sont analysés pour détecter les virus avant d'être accessibles aux autres utilisateurs." project_creation_wizard: - description: "Generate a step-by-step wizard to help project managers fill out a project initiation request." + description: "Générez un assistant étape par étape pour aider les responsables de projet à remplir une demande de lancement de projet." placeholder_users: title: Utilisateurs fictifs description: > @@ -2929,12 +2929,12 @@ fr: instructions_after_error: "Vous pouvez encore essayer de vous authentifier en cliquant sur %{signin}. Si l'erreur persiste, demandez de l'aide à votre administrateur." menus: admin: - ai: "Artificial Intelligence (AI)" + ai: "Intelligence artificielle (IA)" aggregation: "Agrégation" api_and_webhooks: "API et webhooks" mail_notification: "Notifications par e-mail" mails_and_notifications: "E-mails et notifications" - mcp_configurations: "Model Context Protocol (MCP)" + mcp_configurations: "Protocole de contexte de modèle (MCP)" quick_add: label: "Ajouter…" my_account: @@ -2962,7 +2962,7 @@ fr: text_hint: "Les jetons d'API permettent aux applications tierces de communiquer avec cette instance OpenProject via les API REST." static_token_name: "Jeton API" disabled_text: "Les jetons d'API ne sont pas activés par l'administrateur. Veuillez contacter votre administrateur pour utiliser cette fonctionnalité." - add_button: "API token" + add_button: "Jeton d'API" ical: blank_description: "Pour ajouter un jeton iCalendar, abonnez-vous à un calendrier nouveau ou existant à partir du module Calendrier d'un projet. Vous devez disposer des autorisations nécessaires." blank_title: "Pas de jeton iCalendar" @@ -2990,7 +2990,7 @@ fr: removed: "Le jeton client OAuth a été supprimé avec succès" unknown_integration: "Inconnu" token/rss: - add_button: "RSS token" + add_button: "Jeton RSS" blank_description: "Il n'y a pas encore de jeton RSS. Vous pouvez en créer un en utilisant le bouton ci-dessous." blank_title: "Pas de jeton RSS" title: "Flux RSS" diff --git a/config/locales/crowdin/it.yml b/config/locales/crowdin/it.yml index 86b80edbe51..8f995302f2b 100644 --- a/config/locales/crowdin/it.yml +++ b/config/locales/crowdin/it.yml @@ -111,21 +111,21 @@ it: link: "webhook" mcp_configurations: index: - description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." - resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." - resources_submit: "Update resources" - tools_heading: "Tools" - tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." - tools_submit: "Update tools" + description: "Il protocollo di contesto del modello consente agli agenti di intelligenza artificiale di fornire ai propri utenti strumenti e risorse esposti da questa istanza di OpenProject." + resources_heading: "Risorse" + resources_description: "OpenProject implementa i seguenti strumenti. Ognuno di essi può essere abilitato, rinominato e descritto a piacere. Per ulteriori informazioni, consultare la [documentazione sulle risorse MCP](docs_url)." + resources_submit: "Aggiorna risorse" + tools_heading: "Strumenti" + tools_description: "OpenProject implementa i seguenti strumenti. Ognuno di essi può essere abilitato, rinominato e descritto a piacere. Per ulteriori informazioni, consultare la [documentazione sugli strumenti MCP](docs_url)." + tools_submit: "Aggiorna strumenti" multi_update: - success: "MCP configurations were updated successfully." + success: "Configurazioni MCP aggiornate con successo." server_form: - description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + description_caption: "Come verrà descritto il server MCP alle altre applicazioni che vi si collegheranno." + title_caption: "Un breve titolo mostrato alle applicazioni che si connettono al server MCP." update: - failure: "MCP configuration could not be updated." - success: "MCP configuration was updated successfully." + failure: "La configurazione MCP non può essere aggiornata." + success: "La configurazione MCP è stata aggiornata correttamente." scim_clients: authentication_methods: sso: "JWT dal fornitore di identità" @@ -728,11 +728,11 @@ it: create_button: "Crea" name_label: "Nome del token" created_dialog: - one_time_warning: "This is the only time you will see this token. Make sure to copy it now." + one_time_warning: "Questa è l'unica volta in cui vedrai questo token. Assicurati di copiarlo adesso." token/api: title: "Il token API è stato generato" token/rss: - title: "The RSS token has been generated" + title: "Il token RSS è stato generato" failed_to_reset_token: "Impossibile reimpostare il token di accesso: %{error}" failed_to_create_token: "Creazione del token di accesso non riuscita: %{error}" failed_to_revoke_token: "Revoca del token di accesso non riuscita: %{error}" @@ -1251,9 +1251,9 @@ it: port: "Porta" tls_certificate_string: "Certificato SSL del server LDAP" mcp_configuration: - enabled: Enabled - title: Title - description: Description + enabled: Attivata + title: Titolo + description: Descrizione member: roles: "Ruoli" notification: @@ -1512,7 +1512,7 @@ it: even: "deve essere pari." exclusion: "è riservato." feature_disabled: non disponibile. - feature_disabled_for_project: is disabled for this project. + feature_disabled_for_project: non attivo per questo progetto. file_too_large: "è troppo grande (la dimensione massima è %{count} Byte)." filter_does_not_exist: "il filtro non esiste" format: "non corrisponde al formato previsto '%{expected}'." @@ -1945,8 +1945,8 @@ it: one: Token di accesso other: Token di accesso token/rss: - one: "RSS token" - other: "RSS tokens" + one: "Token RSS" + other: "Token RSS" type: one: "Tipo" other: "Tipi" @@ -2451,7 +2451,7 @@ it: baseline_comparison: Confronti della base di riferimento board_view: Schede avanzate calculated_values: Valori calcolati - capture_external_links: Capture External Links + capture_external_links: Intercetta link esterni internal_comments: Commenti interni custom_actions: Azioni personalizzate custom_field_hierarchies: Gerarchie @@ -2461,12 +2461,12 @@ it: edit_attribute_groups: Modifica proprietà gruppo gantt_pdf_export: Esportazione PDF Gantt ldap_groups: Utenti LDAP e sincronizzazione di gruppo - mcp_server: MCP Server + mcp_server: Server MCP nextcloud_sso: Accesso singolo per Nextcloud Storage one_drive_sharepoint_file_storage: Archiviazione file OneDrive/SharePoint placeholder_users: Utenti segnaposto portfolio_management: Gestione del portfolio - project_creation_wizard: Project initiation request + project_creation_wizard: Richiesta di avvio del progetto project_list_sharing: Condivisione elenco progetti readonly_work_packages: Macro-attività di sola lettura scim_api: API server SCIM @@ -2508,7 +2508,7 @@ it: customize_life_cycle: description: "Crea e organizza fasi di progetto diverse da quelle forniti dalla pianificazione del ciclo di progetto PM2." capture_external_links: - description: "Prevent social engineering attacks by capturing and warning about external links before users visit them." + description: "Previeni gli attacchi di ingegneria sociale intercettando e avvisando gli utenti dei link esterni prima che li visitino." work_package_query_relation_columns: description: "Vuoi visualizzare le relazioni o gli elementi figlio nell'elenco di macro-attività?" edit_attribute_groups: @@ -2539,7 +2539,7 @@ it: title: "Azioni personalizzate" description: "Le azioni personalizzate sono scorciatoie che con un clic ti consentono di eseguire una serie di azioni predefinite che è possibile rendere disponibili su determinate macro-attività in base a stato, ruolo, tipo o progetto." mcp_server: - description: "Integrate AI agents with your OpenProject instance through MCP." + description: "Integra gli agenti IA con l'istanza OpenProject tramite MCP." nextcloud_sso: title: "Single Sign-On per Nextcloud Storage" description: "Abilita un'autenticazione veloce e sicura per il tuo archivio Nextcloud con Single Sign-On. Semplifica la gestione degli accessi e migliora la praticità per l'utente." @@ -2552,7 +2552,7 @@ it: virus_scanning: description: "Assicurati che i file caricati in OpenProject vengano scansionati alla ricerca di virus prima di renderli accessibili ad altri utenti." project_creation_wizard: - description: "Generate a step-by-step wizard to help project managers fill out a project initiation request." + description: "Genera una procedura guidata passo dopo passo per aiutare i project manager a compilare una richiesta di avvio del progetto." placeholder_users: title: Utenti segnaposto description: > @@ -2852,15 +2852,15 @@ it: #We need to include the version to invalidate outdated translations in other locales "17_1": new_features_title: > - The release contains various new features and improvements, such as: + Questa versione contiene diverse nuove funzionalità e miglioramenti, come ad esempio: new_features_list: - line_0: Automated project initiation (Enterprise add-on). - line_1: "Meetings: add new or existing work packages as outcomes." - line_2: "Meetings: show participant responses in iCal subscriptions." - line_3: "Recurring meetings: duplicate agenda items to the next occurrence." - line_4: "Release to Community: Attribute highlighting." - line_5: Warning before opening external links in user-provided content (Enterprise add-on). - line_6: Improved performance and user experience, including the Activity tab and Documents module. + line_0: Avvio automatico del progetto (componente aggiuntivo Enterprise). + line_1: "Riunioni: aggiungi macro-attività nuove o esistenti come risultati." + line_2: "Riunioni: mostra le risposte dei partecipanti nelle sottoscrizioni iCal." + line_3: "Riunioni ricorrenti: duplica i punti dell'ordine del giorno della prossima ricorrenza." + line_4: "Rilascio alla community: evidenziazione degli attributi." + line_5: Avviso prima dell'apertura di link esterni nei contenuti forniti dall'utente (componente aggiuntivo Enterprise). + line_6: Prestazioni ed esperienza utente migliorate, compresa la scheda Attività e il modulo Documenti. links: upgrade_enterprise_edition: "Aggiorna ad Enterprise edition" postgres_migration: "Migrazione dell'installazione su PostgreSQL" @@ -2928,17 +2928,17 @@ it: instructions_after_error: "Si può provare ad accedere nuovamente cliccando %{signin}. Se l'errore persiste, chiedi aiuto al tuo amministratore." menus: admin: - ai: "Artificial Intelligence (AI)" + ai: "Intelligenza artificiale (IA)" aggregation: "Aggregazione" api_and_webhooks: "API e webhooks" mail_notification: "Notifiche email" mails_and_notifications: "Email e notifiche" - mcp_configurations: "Model Context Protocol (MCP)" + mcp_configurations: "Modello di protocollo contestuale (MCP - Model Context Protocol)" quick_add: label: "Aggiungi…" my_account: access_tokens: - description: "Provider tokens are issued by OpenProject, allowing other applications to access it. Client tokens are issued by other applications, allowing OpenProject to access them." + description: "I token provider vengono emessi da OpenProject, consentendo ad altre applicazioni di accedervi. I token client vengono emessi da altre applicazioni, consentendo a OpenProject di accedervi." no_results: title: "Nessun token di accesso da visualizzare" description: "Sono stati tutti disabilitati. Possono essere nuovamente abilitati dal menu amministrazione." @@ -2950,53 +2950,53 @@ it: simple_revoke_confirmation: "Revocare il token?" tabs: client: - title: "Client tokens" + title: "Token client" provider: - title: "Provider tokens" + title: "Token provider" token/api: - blank_description: "There is no API token yet. You can create one using the button below." - blank_title: "No API token" + blank_description: "Non esiste ancora un token API. Puoi crearne uno utilizzando il pulsante qui sotto." + blank_title: "Nessun token API" title: "API" - table_title: "API tokens" + table_title: "Token API" text_hint: "I token API consentono alle applicazioni di terze parti di comunicare con questa istanza OpenProject tramite le API REST." - static_token_name: "API token" + static_token_name: "Token API" disabled_text: "I token API non sono abilitati dall'amministratore. Contatta il tuo amministratore per utilizzare questa funzione." - add_button: "API token" + add_button: "Token API" ical: - blank_description: "To add an iCalendar token, subscribe to a new or existing calendar from within the Calendar module of a project. You must have the necessary permissions." - blank_title: "No iCalendar token" + blank_description: "Per aggiungere un token iCalendar, iscriviti a un calendario nuovo o esistente dall'interno del modulo Calendario di un progetto. È necessario disporre delle autorizzazioni necessarie." + blank_title: "Nessun token iCalendar" title: "iCalendar" - table_title: "iCalendar tokens" + table_title: "Token iCalendar" text_hint_link: "I token iCalendar consentono agli utenti di [iscriversi ai calendari di OpenProject](docs_url) e visualizzare informazioni aggiornate sulle macro-attività da client esterni." disabled_text: "Le iscrizioni iCalendar non sono abilitate dall'amministratore. Contatta il tuo amministratore per utilizzare questa funzione." oauth_application: - active_tokens: "Active tokens" - blank_description: "There is no third-party application access configured and active for you." - blank_title: "No OAuth application token" - last_used_at: "Last used at" + active_tokens: "Token attivi" + blank_description: "Non è configurato e attivo alcun accesso ad applicazioni di terze parti per te." + blank_title: "Nessun token di applicazione OAuth" + last_used_at: "Ultimo utilizzo:" title: "OAuth" - table_title: "OAuth application tokens" - text_hint: "OAuth application tokens allow third-party applications to connect with this OpenProject instance." + table_title: "Token di applicazione OAuth" + text_hint: "I token di applicazione OAuth consentono alle applicazioni di terze parti di connettersi con questa istanza OpenProject." oauth_client: - blank_description: "There are no OAuth client tokens yet." - blank_title: "No OAuth client tokens" - failed: "An error occurred and the token couldn't be removed. Please try again later." - integration_type: "Integration type" - table_title: "OAuth client tokens" - text_hint: "OAuth client tokens allow this OpenProject instance to connect with external applications, such as file storages." + blank_description: "Non ci sono ancora token client OAuth." + blank_title: "Nessun token client OAuth" + failed: "Si è verificato un errore e non è stato possibile rimuovere il token. Riprova più tardi." + integration_type: "Tipo di integrazione" + table_title: "Token client OAuth" + text_hint: "I token client OAuth consentono a questa istanza di OpenProject di connettersi con applicazioni esterne, come gli archivi di file." title: "OAuth" - remove_token: "Do you really want to remove this token? You will need to login again on %{integration}." - removed: "OAuth client token successfully removed" - unknown_integration: "Unknown" + remove_token: "Vuoi davvero rimuovere questo token? Dovrai effettuare nuovamente l'accesso su %{integration}." + removed: "Il token del cliente OAuth è stato rimosso con successo" + unknown_integration: "Sconosciuto" token/rss: - add_button: "RSS token" - blank_description: "There is no RSS token yet. You can create one using the button below." - blank_title: "No RSS token" + add_button: "Token RSS" + blank_description: "Non esiste ancora un token RSS. Puoi crearne uno utilizzando il pulsante qui sotto." + blank_title: "Nessun token RSS" title: "RSS" - table_title: "RSS tokens" - text_hint: "RSS tokens allow users to keep up with the latest changes in this OpenProject instance via an external RSS reader." - static_token_name: "RSS token" - disabled_text: "RSS tokens are not enabled by the administrator. Please contact your administrator to use this feature." + table_title: "Token RSS" + text_hint: "I token RSS consentono agli utenti di tenere il passo con le ultime modifiche in questa istanza di OpenProject tramite un lettore RSS esterno." + static_token_name: "Token RSS" + disabled_text: "I token RSS non sono abilitati dall'amministratore. Contatta il tuo amministratore per utilizzare questa funzione." storages: unknown_storage: "Archivio sconosciuto" notifications: @@ -3314,7 +3314,7 @@ it: label_journal_diff: "Confronto Descrizione" label_language: "Lingua" label_languages: "Lingue" - label_external_links: "External links" + label_external_links: "Link esterni" label_locale: "Lingua e paese" label_jump_to_a_project: "Salta ad altro progetto..." label_keyword_plural: "Parole chiave" @@ -4308,9 +4308,9 @@ it: setting_allowed_link_protocols: "Protocolli di collegamento consentiti" setting_allowed_link_protocols_text_html: >- Consenti che questi protocolli vengano visualizzati come link nelle descrizioni delle macro-attività, nei campi di testo lunghi e nei commenti. Ad esempio, %{tel_code} o %{element_code}. Inserisci un protocollo per riga.
    I protocolli %{http_code}, %{https_code} e %{mailto_code} sono sempre consentiti. - setting_capture_external_links: "Capture external links" + setting_capture_external_links: "Intercetta link esterni" setting_capture_external_links_text: > - When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + Se l'opzione è abilitata, tutti i link esterni in testo formattato verranno reindirizzati a una pagina di avviso prima di uscire dall'applicazione. Questo aiuta a proteggere gli utenti da siti web esterni potenzialmente dannosi. setting_after_first_login_redirect_url: "Reindirizzamento di primo accesso" setting_after_first_login_redirect_url_text_html: > Imposta un percorso per reindirizzare gli utenti dopo il loro primo accesso. Se vuoto, reindirizza alla home page per il tour di onboarding.
    Esempio: /my/page @@ -4324,16 +4324,16 @@ it: Se CORS è abilitato, queste sono le origini che possono accedere alle API di OpenProject.
    Controlla la Documentazione sull'intestazione dell'origine per sapere come specificare i valori previsti. setting_apiv3_write_readonly_attributes: "Accesso in scrittura agli attributi di sola lettura" setting_apiv3_write_readonly_attributes_instructions: > - If enabled, the API will allow administrators to write static read-only attributes during creation, such as createdAt and author. + Se l'opzione è abilitata, l'API consentirà agli amministratori di scrivere attributi statici di sola lettura durante la creazione, come createdAt e author. setting_apiv3_write_readonly_attributes_warning: > - This setting has a use-case for e.g., importing data, but allows administrators to impersonate the creation of items as other users. All creation requests are being logged however with the true author. + Questa impostazione è utile, ad esempio, per l'importazione di dati, ma consente agli amministratori di impersonare altri utenti nella creazione di elementi. Tutte le richieste di creazione vengono comunque registrate con il vero autore. setting_apiv3_write_readonly_attributes_additional: > - For more information on attributes and supported resources, please see the %{api_documentation_link}. + Per maggiori informazioni sugli attributi e sulle risorse supportate, consulta %{api_documentation_link}. setting_apiv3_max_page_size: "Dimensione massima della pagina API" setting_apiv3_max_page_size_instructions: > - Set the maximum page size the API will respond with. It will not be possible to perform API requests that return more values on a single page. + Imposta la dimensione massima della pagina con cui l'API risponderà. Non sarà possibile eseguire richieste API che restituiscano più valori in una singola pagina. setting_apiv3_max_page_size_warning: > - Please only change this value if you are sure why you need it. Setting to a high value will result in significant performance impacts, while a value lower than the per page options will cause errors in paginated views. + Modifica questo valore solo se hai certezza del motivo per cui ti serve. Un valore elevato avrà un impatto significativo sulle prestazioni, mentre un valore inferiore alle opzioni per pagina causerà errori nelle visualizzazioni paginate. setting_apiv3_docs: "Documentazione" setting_apiv3_docs_enabled: "Abilita pagina di documenti" setting_apiv3_docs_enabled_instructions_html: > @@ -4518,7 +4518,7 @@ it: omniauth_direct_login_hint_html: > Se questa opzione è attiva, le richieste di accesso saranno reindirizzate al fornitore omniauth configurato. Il menu a tendina di accesso e la pagina di accesso saranno disabilitate.
    Nota: a meno che non si disabiliti anche l'accesso con password, con questa opzione abilitata, gli utenti possono ancora accedere internamente visitando la pagina di accesso %{internal_path}. remapping_existing_users_hint: > - If enabled, allows any configured identity provider to login existing users based on their username, even if the user never signed in through that provider before. This can be useful when migrating the OpenProject instance to a new SSO provider, but is not recommended when using a provider that is not trusted by all users of your instance. + Se abilitata, consente a qualsiasi provider di identità configurato di far accedere gli utenti esistenti in base al loro nome utente, anche se l'utente non ha mai effettuato l'accesso tramite quel provider in precedenza. Questo può essere utile durante la migrazione dell'istanza OpenProject a un nuovo provider SSO, ma non è raccomandato quando si utilizza un provider non considerato affidabile da tutti gli utenti dell'istanza. attachments: whitelist_text_html: > Definisci un elenco di estensioni di file valide e/o tipi MIME per i file caricati.
    Inserisci le estensioni dei file (ad es. %{ext_example}) o i tipi MIME (ad es. %{mime_example}).
    Lascia vuoto per consentire il caricamento di qualsiasi tipo di file. Sono consentiti più valori (una riga per ogni valore). @@ -4624,7 +4624,7 @@ it: project_mandate: "Mandato del progetto" submission: description_template: > - **This work package was automatically created upon completion of the %{wizard_name} workflow.** A PDF artifact containing all submitted information has been generated and attached to this work package for reference and audit purposes. If you need to update or re-run the initiation steps, you can reopen the wizard at any time by using the link below: + **Questa macro-attività è stata creata automaticamente al completamento del flusso di lavoro %{wizard_name}.** È stato generato un artefatto PDF contenente tutte le informazioni inviate ed è stato allegato a questa macro-attività a fini di consultazione e audit. Se devi aggiornare o rieseguire i passaggi di avvio, puoi riaprire la procedura guidata in qualsiasi momento utilizzando il link qui sotto: description: "Quando un utente invia una richiesta di avvio del progetto, viene creata una nuova macro-attività con l'artefatto della richiesta allegato come file PDF. Le impostazioni di seguito definiscono il tipo, lo stato e l'assegnatario di questa nuova macro-attività." work_package_type: "Tipo di macro-attività" work_package_type_caption: "Il tipo di macro-attività che dovrebbe essere utilizzato per memorizzare l'artefatto completato." @@ -4883,8 +4883,8 @@ it: other: "bloccato temporaneamente (%{count} tentativi di accesso falliti)" confirm_status_change: "Stai per modificare lo stato di '%{name}'. Continuare?" deleted: "Utente cancellato" - error_status_change_self: "You cannot change your own user status." - error_admin_change_on_non_admin: "Only administrators can change the status of administrator users." + error_status_change_self: "Non puoi cambiare il tuo stato utente." + error_admin_change_on_non_admin: "Solo gli amministratori possono modificare lo stato degli utenti amministratori." error_status_change_failed: "Il cambiamento dello stato dell'utente è fallito a causa dei seguenti errori: %{errors}" invite: Invita l'utente via e-mail invited: invitato @@ -5290,7 +5290,7 @@ it: hashed_token: display_value_placeholder: "***" external_link_warning: - title: "Leaving OpenProject" - warning_message: "You are about to leave OpenProject and visit an external website. Please be aware that external websites are not under our control and may have different privacy and security policies." - continue_message: "Are you sure you want to proceed to the following external link?" - continue_button: "Continue to external website" + title: "Uscita da OpenProject" + warning_message: "Stai per uscire da OpenProject e visitare un sito web esterno. Tieni presente che i siti web esterni non sono sotto il nostro controllo e potrebbero avere politiche sulla privacy e sulla sicurezza diverse." + continue_message: "Vuoi davvero procedere al seguente link esterno?" + continue_button: "Prosegui con il sito web esterno" diff --git a/config/locales/crowdin/js-it.yml b/config/locales/crowdin/js-it.yml index 64ba7007956..6e84a66fcad 100644 --- a/config/locales/crowdin/js-it.yml +++ b/config/locales/crowdin/js-it.yml @@ -203,8 +203,8 @@ it: add_table: "Aggiungi la tabella dei pacchetti di lavoro correlati" edit_query: "Modifica query" new_group: "Nuovo gruppo" - delete_group: "Delete group" - remove_attribute: "Remove from group" + delete_group: "Elimina gruppo" + remove_attribute: "Rimuovi dal gruppo" reset_to_defaults: "Ripristino predefinite" working_days: calendar: @@ -426,7 +426,7 @@ it: label_remove_row: "Rimuovi riga" label_report: "Segnalano" label_repository_plural: "Archivi" - label_resize_project_menu: "Resize project menu" + label_resize_project_menu: "Ridimensiona il menu del progetto" label_save_as: "Salva come" label_search_columns: "Cerca una colonna" label_select_watcher: "Seleziona un osservatore..." diff --git a/config/locales/crowdin/js-ko.yml b/config/locales/crowdin/js-ko.yml index fbc8815eab1..28a1ad4f171 100644 --- a/config/locales/crowdin/js-ko.yml +++ b/config/locales/crowdin/js-ko.yml @@ -203,8 +203,8 @@ ko: add_table: "관련 작업 패키지의 테이블 추가" edit_query: "쿼리 편집" new_group: "새 그룹" - delete_group: "Delete group" - remove_attribute: "Remove from group" + delete_group: "그룹 삭제" + remove_attribute: "그룹에서 제거" reset_to_defaults: "기본값으로 초기화" working_days: calendar: @@ -426,7 +426,7 @@ ko: label_remove_row: "행 제거" label_report: "보고서" label_repository_plural: "리포지토리" - label_resize_project_menu: "Resize project menu" + label_resize_project_menu: "프로젝트 메뉴 크기 조정" label_save_as: "다른 이름으로 저장" label_search_columns: "열 검색" label_select_watcher: "주시하는 사람을 선택" diff --git a/config/locales/crowdin/js-pl.yml b/config/locales/crowdin/js-pl.yml index 92e4544b364..2ce5b854a69 100644 --- a/config/locales/crowdin/js-pl.yml +++ b/config/locales/crowdin/js-pl.yml @@ -203,8 +203,8 @@ pl: add_table: "Dodaj tabelę powiązanych pakietów roboczych" edit_query: "Edytuj zapytanie" new_group: "Nowa Grupa" - delete_group: "Delete group" - remove_attribute: "Remove from group" + delete_group: "Usuń grupę" + remove_attribute: "Usuń z grupy" reset_to_defaults: "Resetuj do ustawień domyślnych" working_days: calendar: @@ -426,7 +426,7 @@ pl: label_remove_row: "Usuń wiersz" label_report: "Raport" label_repository_plural: "Repozytoria" - label_resize_project_menu: "Resize project menu" + label_resize_project_menu: "Zmień rozmiar menu projektu" label_save_as: "Zapisz jako" label_search_columns: "Wyszukaj kolumnę" label_select_watcher: "Wybierz obserwatora..." @@ -1132,8 +1132,8 @@ pl: global_search: all_projects: "We wszystkich projektach" close_search: "Zakończ wyszukiwanie" - items_available: "%{count} items available" - direct_hit_available: "Work package with exact ID found. Press Enter to open it." + items_available: "Dostępne elementy: %{count}" + direct_hit_available: "Znaleziono pakiet roboczy o dokładnym identyfikatorze. Aby go otworzyć, naciśnij klawisz Enter." current_project_and_all_descendants: "W tym projekcie + pod projekcie" current_project: "W tym projekcie" recently_viewed: "Ostatnio wyświetlane" diff --git a/config/locales/crowdin/js-tr.yml b/config/locales/crowdin/js-tr.yml index bc930bfc83a..22ed8514c11 100644 --- a/config/locales/crowdin/js-tr.yml +++ b/config/locales/crowdin/js-tr.yml @@ -464,7 +464,7 @@ tr: label_work_package_plural: "İş paketleri" label_watch: "Takip et" label_watch_work_package: "İş paketini takip et" - label_watcher_added_successfully: "Takipçi başarıyla eklendi!" + label_watcher_added_successfully: "Gözlemci başarıyla eklendi!" label_watcher_deleted_successfully: "Takipçi başarıyla silindi!" label_work_package_details_you_are_here: "%{type} %{subject} için %{tab} sekmesindesiniz." label_work_package_context_menu: "İş paketi bağlam menüsü" @@ -763,8 +763,8 @@ tr: toggle_description: "İlişki açıklamasını göster" update_relation: "İlişki türünü değiştirmek için tıklayın" show_relations: "İlişkileri göster" - add_predecessor: "Öncelik ekle" - add_successor: "Yedek personel ekle" + add_predecessor: "Ekle (Öncül)" + add_successor: "Ekle (Ardıl)" remove: "İlişkiyi kaldır" save: "İlişkiyi kaydet" abort: "İptal" diff --git a/config/locales/crowdin/js-vi.yml b/config/locales/crowdin/js-vi.yml index 46e66a064c0..fd2da032e6c 100644 --- a/config/locales/crowdin/js-vi.yml +++ b/config/locales/crowdin/js-vi.yml @@ -26,12 +26,12 @@ vi: loading: "Đang tải…" updating: "Đang cập nhật…" attachments: - delete: "Xóa tập tin đính kèm" + delete: "Xóa tệp đính kèm" delete_confirmation: | - Bạn có chắc chắn muốn xóa tập tin này không? Hành động này không thể hoàn tác. + Bạn có chắc chắn muốn xóa tập tin này? Hành động này không thể đảo ngược. draggable_hint: | - Kéo trên trường được soạn thảo để chèn ảnh hoặc file đính kèm. Trường soạn thảo đang được đóng sẽ được mở lại khi bạn kéo thả. - quarantined_hint: "Tập tin đã bị cách ly do phát hiện virus. Nó không khả dụng để tải xuống." + Kéo trường soạn thảo vào hình ảnh nội tuyến hoặc tệp đính kèm tham chiếu. Các trường soạn thảo đã đóng sẽ được mở trong khi bạn tiếp tục kéo. + quarantined_hint: "Tệp đã được cách ly vì đã tìm thấy vi-rút. Nó không có sẵn để tải về." autocomplete_ng_select: add_tag: "Thêm mục" clear_all: "Xóa tất cả" @@ -45,52 +45,52 @@ vi: remove: "Xoá %{name}" active: "Kích hoạt %{label} %{name}" backup: - attachments_disabled: Các tệp đính kèm có thể không được thêm vào vì chúng vượt quá kích thước tối đa được phép. Bạn có thể thay đổi điều này thông qua cấu hình (yêu cầu khởi động lại máy chủ). + attachments_disabled: Tệp đính kèm có thể không được đưa vào vì chúng vượt quá kích thước tổng thể tối đa cho phép. Bạn có thể thay đổi điều này thông qua cấu hình (yêu cầu khởi động lại máy chủ). info: > - Bạn có thể kích hoạt một bản sao lưu ở đây. Quá trình có thể mất một chút thời gian tùy thuộc vào lượng dữ liệu (đặc biệt là tệp đính kèm) bạn có. Bạn sẽ nhận được một email sau khi nó sẵn sàng. + Bạn có thể kích hoạt một bản sao lưu ở đây. Quá trình này có thể mất một chút thời gian tùy thuộc vào lượng dữ liệu (đặc biệt là tệp đính kèm) mà bạn có. Bạn sẽ nhận được email khi nó đã sẵn sàng. note: > - Một bản sao lưu mới sẽ ghi đè cái trước đó. Số lượng bản sao lưu mỗi ngày là bị hạn chế. - last_backup: Sao lưu gần đây - last_backup_from: Sao lưu gần đây nhất từ - title: Sao lưu OpenProject - options: Tuỳ chọn - include_attachments: Bao gồm các tệp đính kèm + Bản sao lưu mới sẽ ghi đè lên bất kỳ bản sao lưu nào trước đó. Chỉ có thể yêu cầu một số lượng bản sao lưu giới hạn mỗi ngày. + last_backup: Sao lưu lần cuối + last_backup_from: Sao lưu lần cuối từ + title: Sao lưu dự án mở + options: tùy chọn + include_attachments: Bao gồm tệp đính kèm download_backup: Tải xuống bản sao lưu request_backup: Yêu cầu sao lưu - close_popup_title: "Đóng" + close_popup_title: "Đóng cửa sổ bật lên" close_filter_title: "Đóng bộ lọc" close_form_title: "Đóng biểu mẫu" button_add_watcher: "Thêm người theo dõi" button_add: "Thêm" button_back: "Quay lại" button_back_to_list_view: "Quay lại danh sách" - button_cancel: "Hủy" + button_cancel: "Hủy bỏ" button_close: "Đóng" button_change_project: "Chuyển sang dự án khác" - button_check_all: "Đánh dấu tất cả" - button_configure-form: "Cấu hình biểu mẫu" + button_check_all: "Kiểm tra tất cả" + button_configure-form: "Định cấu hình biểu mẫu" button_confirm: "Xác nhận" - button_continue: "Tiếp tục" - button_copy: "Sao chép" - button_copy_to_clipboard: "Sao chép vào bảng tạm" + button_continue: "tiếp tục" + button_copy: "sao chép" + button_copy_to_clipboard: "Sao chép vào khay nhớ tạm" button_copy_link_to_clipboard: "Sao chép liên kết vào bảng tạm" - button_copy_to_other_project: "Sao chép trong một dự án khác" + button_copy_to_other_project: "Trùng lặp trong một dự án khác" button_custom-fields: "Tùy chỉnh mục" - button_delete: "Xoá" + button_delete: "xóa" button_delete_watcher: "Xóa người xem" button_details_view: "Thông tin chi tiết xem" - button_duplicate: "Nhân đôi" + button_duplicate: "trùng lặp" button_edit: "Chỉnh sửa" - button_filter: "Bộ lọc" + button_filter: "bộ lọc" button_collapse_all: "Thu gọn tất cả" button_expand_all: "Mở rộng tất cả" button_advanced_filter: "Bộ lọc nâng cao" button_list_view: "Xem kiểu danh sách" button_show_view: "Xem toàn màn hình" - button_log_time: "Thời gian truy cập" - button_start_timer: "Start timer" - button_stop_timer: "Stop timer" - button_more: "Xem thêm" + button_log_time: "Đăng nhập thời gian" + button_start_timer: "Bắt đầu hẹn giờ" + button_stop_timer: "Dừng hẹn giờ" + button_more: "hơn thế nữa" button_open_details: "Mở thông tin chi tiết xem" button_close_details: "Đóng xem chi tiết" button_open_fullscreen: "Mở chế độ toàn màn hình" @@ -99,14 +99,14 @@ vi: button_show_table: "Hiển thị chế độ xem bảng" button_show_gantt: "Hiển thị chế độ xem Gantt" button_show_fullscreen: "Hiển thị chế độ xem toàn màn hình" - button_more_actions: "Thao tác thêm" - button_quote: "Trích dẫn" - button_save: "Lưu" - button_settings: "Cài đặt" + button_more_actions: "Thêm hành động" + button_quote: "trích dẫn" + button_save: "lưu lại" + button_settings: "cài đặt" button_uncheck_all: "Bỏ chọn tất cả" button_update: "Cập Nhật" - button_export-atom: "Tải về Atom" - button_generate_pdf: "Generate PDF" + button_export-atom: "Tải xuống nguyên tử" + button_generate_pdf: "Tạo PDF" button_create: "Tạo mới" card: add_new: "Thêm thẻ mới" @@ -114,138 +114,139 @@ vi: inline: "Đánh dấu nội tuyến:" entire_card_by: "Toàn bộ thẻ của" remove_from_list: "Xóa thẻ khỏi danh sách" - caption_rate_history: "Lịch sử tỷ lệ" + caption_rate_history: "Lịch sử giá" clipboard: browser_error: "Trình duyệt của bạn không hỗ trợ sao chép vào clipboard. Vui lòng sao chép thủ công: %{content}" - copied_successful: "Sao chép vào clipboard thành công!" + copied_successful: "Đã sao chép thành công vào clipboard!" chart: type: "Kiểu đồ thị" axis_criteria: "Tiêu chí trục" - modal_title: "Bảng cấu hình công việc" + modal_title: "Cấu hình biểu đồ gói công việc" types: - line: "Dòng" - horizontal_bar: "Cột ngang" - bar: "Thanh" - pie: "Biểu đồ tròn" - doughnut: "Bánh vòng" - radar: "Ra đa" + line: "dòng" + horizontal_bar: "Thanh ngang" + bar: "thanh" + pie: "bánh" + doughnut: "bánh rán" + radar: "ra đa" polar_area: "Vùng cực" tabs: - graph_settings: "Tổng quan" - dataset: "Tập dữ liệu %{number}" + graph_settings: "chung" + dataset: "Bộ dữ liệu %{number}" errors: - could_not_load: "Dữ liệu để hiển thị biểu đồ không thể được tải. Có thể thiếu quyền cần thiết." + could_not_load: "Không thể tải dữ liệu để hiển thị biểu đồ. Các quyền cần thiết có thể bị thiếu." description_available_columns: "Cột có sẵn" description_current_position: "Bạn đang ở đây:" - description_select_work_package: "Chọn work package #%{id}" - description_subwork_package: "Con của work package #%{id}" + description_select_work_package: "Chọn gói công việc #%{id}" + description_subwork_package: "Gói công việc con #%{id}" editor: revisions: "Hiển thị các sửa đổi cục bộ" no_revisions: "Không tìm thấy sửa đổi cục bộ" preview: "Bật tắt chế độ xem trước" - source_code: "Chuyển đổi chế độ mã Markdown" + source_code: "Chuyển đổi chế độ nguồn Markdown" error_saving_failed: "Lưu tài liệu không thành công với lỗi sau: %{error}" ckeditor_error: "Đã xảy ra lỗi trong CKEditor" mode: - manual: "Chuyển sang mã Markdown" - wysiwyg: "Chuyển sang trình chỉnh sửa WYSIWYG" + manual: "Chuyển sang nguồn Markdown" + wysiwyg: "Chuyển sang trình soạn thảo WYSIWYG" macro: error: "Không thể mở rộng macro: %{message}" attribute_reference: - macro_help_tooltip: "Đoạn văn bản này đang được hiển thị động bởi macro." - not_found: "Tài nguyên yêu cầu không thể được tìm thấy" + macro_help_tooltip: "Đoạn văn bản này đang được macro hiển thị động." + not_found: "Không thể tìm thấy tài nguyên được yêu cầu" nested_macro: "Macro này đang tham chiếu đệ quy %{model} %{id}." - invalid_attribute: "Thuộc tính '%{name}' đã chọn không tồn tại." + invalid_attribute: "Thuộc tính đã chọn '%{name}' không tồn tại." child_pages: button: "Liên kết đến trang con" - include_parent: "Bao gồm trang trước" - text: "[Placeholder] Liên kết tới trang con của" - page: "Trang wiki" + include_parent: "Bao gồm cha mẹ" + text: "[Placeholder] Liên kết tới các trang con của" + page: "trang Wiki" this_page: "trang này" hint: | - Để trường này rỗng đẻ liệt kêtất cả các trang con của trang hiện tại. Nếu bạn muốn tham chiếu đến một trang khác, hãy cung cấp tiêu đề hoặc lời bình của trang. + Để trống trường này để liệt kê tất cả các trang con của trang hiện tại. Nếu bạn muốn tham khảo một trang khác, hãy cung cấp tiêu đề hoặc phần mở rộng của trang đó. code_block: button: "Chèn đoạn mã" - title: "Insert / sửa đoạn mã" + title: "Chèn/sửa đoạn mã" language: "Định dạng ngôn ngữ" - language_hint: "Nhập ngôn ngữ định dạng sẽ được sử dụng để làm nổi bật (nếu được hỗ trợ)." + language_hint: "Nhập ngôn ngữ định dạng sẽ được sử dụng để đánh dấu (nếu được hỗ trợ)." dropdown: macros: "Macro" - chose_macro: "Chọn macro" + chose_macro: "Chọn vĩ mô" toc: "Mục lục" - toolbar_help: "Bấm vào để chọn tiện ích và hiển thị thanh công cụ. Bấm đúp để chỉnh sửa tiện ích" + toolbar_help: "Bấm để chọn widget và hiển thị thanh công cụ. Nhấp đúp để chỉnh sửa tiện ích" wiki_page_include: button: "Bao gồm các nội dung của một trang wiki khác" - text: "[Placeholder] bao gồm trang wiki của" - page: "Trang wiki" + text: "[Placeholder] Bao gồm trang wiki của" + page: "trang Wiki" not_set: "(Trang chưa được thiết lập)" hint: | - Bao gồm các nội dung của các trang wiki khác bằng cách xác định các tiêu đề hoặc bình luận. Bạn có thể bao gồm các trang wiki của dự án khác bằng cách tách chúng với một dấu phẩy như ví dụ sau. + Bao gồm nội dung của một trang wiki khác bằng cách chỉ định tiêu đề hoặc phần mở rộng của nó. + Bạn có thể bao gồm trang wiki của một dự án khác bằng cách phân tách chúng bằng dấu hai chấm như ví dụ sau. work_package_button: - button: "Chèn nút tạo gói" - type: "Kiểu của gói công việc" + button: "Chèn nút tạo gói công việc" + type: "Loại gói công việc" button_style: "Sử dụng kiểu nút" - button_style_hint: "Tùy chọn: Kiểm tra xem macro xuất hiện như là một nút, chứ không phải là một liên kết." - without_type: "Work package liên quan" - with_type: "Tạo gói công việc (loại: %{typename})" + button_style_hint: "Tùy chọn: Chọn để làm cho macro xuất hiện dưới dạng nút chứ không phải dưới dạng liên kết." + without_type: "Tạo gói công việc" + with_type: "Tạo gói công việc (Loại: %{typename})" embedded_table: - button: "Nhúng danh sách gói công việc dưới dạng bảng" - text: "[Placeholder] gói công việc nhúng" + button: "Nhúng bảng gói công việc" + text: "[Placeholder] Bảng gói công việc được nhúng" embedded_calendar: - text: "[Placeholder] Lịch đã nhúng" + text: "[Placeholder] Lịch nhúng" admin: type_form: custom_field: "Tùy chỉnh mục" inactive: "Không hoạt động" drag_to_activate: "Kéo các trường từ đây để kích hoạt chúng" add_group: "Thêm 1 nhóm thuộc tính" - add_table: "Thêm bảng các công việc liên quan" + add_table: "Thêm bảng các gói công việc liên quan" edit_query: "Chỉnh sửa truy vấn" new_group: "Nhóm mới" - delete_group: "Delete group" - remove_attribute: "Remove from group" + delete_group: "Xóa nhóm" + remove_attribute: "Xóa khỏi nhóm" reset_to_defaults: "Đặt lại về mặc định" working_days: calendar: - empty_state_header: "Ngày không làm việc" - empty_state_description: 'Không có ngày không làm việc cụ thể nào được định nghĩa cho năm nay. Nhấp vào "+ Ngày không làm việc" bên dưới để thêm ngày.' + empty_state_header: "Những ngày không làm việc" + empty_state_description: 'Không có ngày không làm việc cụ thể nào được xác định cho năm nay. Bấm vào "+ Ngày không làm việc" bên dưới để thêm ngày.' new_date: "(mới)" add_non_working_day: "Ngày không làm việc" - already_added_error: "Một ngày không làm việc cho ngày này đã tồn tại. Chỉ có thể tạo một ngày không làm việc cho mỗi ngày duy nhất." + already_added_error: "Ngày không làm việc cho ngày này đã tồn tại. Chỉ có thể có một ngày không làm việc được tạo cho mỗi ngày duy nhất." change_button: "Lưu và lên lịch lại" change_title: "Thay đổi ngày làm việc" - removed_title: "Bạn sẽ xóa các ngày sau khỏi danh sách ngày không làm việc:" - change_description: "Changing which days of the week are considered working days or non-working days can affect the start and finish days of all work packages and life cycles in all projects in this instance." + removed_title: "Bạn sẽ xóa những ngày sau khỏi danh sách những ngày không làm việc:" + change_description: "Việc thay đổi ngày trong tuần được coi là ngày làm việc hay ngày không làm việc có thể ảnh hưởng đến ngày bắt đầu và ngày kết thúc của tất cả các gói công việc và vòng đời trong tất cả các dự án trong trường hợp này." warning: > - The changes might take some time to take effect. You will be notified when all relevant work packages and project life cycles have been updated. - Are you sure you want to continue? + Những thay đổi có thể mất một thời gian để có hiệu lực. Bạn sẽ được thông báo khi tất cả các gói công việc có liên quan và vòng đời dự án đã được cập nhật. + Bạn có chắc chắn muốn tiếp tục không? work_packages_settings: warning_progress_calculation_mode_change_from_status_to_field_html: >- - Thay đổi chế độ tính toán tiến độ từ dựa trên trạng thái sang dựa trên công việc sẽ làm cho trường % Hoàn thành có thể chỉnh sửa tự do. Nếu bạn tùy ý nhập giá trị cho Công việc hoặc Công việc còn lại, chúng cũng sẽ được liên kết với % Hoàn thành. Thay đổi Công việc còn lại sau đó có thể cập nhật % Hoàn thành. + Việc thay đổi chế độ tính toán tiến độ từ dựa trên trạng thái sang dựa trên công việc sẽ làm cho trường % Hoàn thành có thể chỉnh sửa tự do. Nếu bạn tùy ý nhập các giá trị cho Work hoặc Công việc còn lại, thì chúng cũng sẽ được liên kết với % Hoàn thành. Việc thay đổi Công việc còn lại sau đó có thể cập nhật % Hoàn thành. warning_progress_calculation_mode_change_from_field_to_status_html: >- - Thay đổi chế độ tính toán tiến độ từ dựa trên công việc sang dựa trên trạng thái sẽ dẫn đến việc tất cả các giá trị hiện tại của % Hoàn thành bị mất và thay thế bằng các giá trị liên kết với từng trạng thái. Các giá trị hiện tại cho Công việc còn lại cũng có thể được tính lại để phản ánh sự thay đổi này. Hành động này không thể hoàn tác. + Việc thay đổi chế độ tính toán tiến độ từ dựa trên công việc sang dựa trên trạng thái sẽ khiến tất cả các giá trị % Hoàn thành hiện có bị mất và được thay thế bằng các giá trị được liên kết với từng trạng thái. Các giá trị hiện tại cho Công việc còn lại cũng có thể được tính toán lại để phản ánh sự thay đổi này. Hành động này không thể đảo ngược. custom_actions: date: - specific: "vào" + specific: "trên" current_date: "Ngày hiện tại" error: internal: "Lỗi nội bộ đã xảy ra." - cannot_save_changes_with_message: "Không thể lưu thay đổi của bạn do lỗi sau: %{error}" + cannot_save_changes_with_message: "Không thể lưu các thay đổi của bạn do lỗi sau: %{error}" query_saving: "Các tiêu chí không thể được lưu." - embedded_table_loading: "Không thể nạp được vùng nhìn nhúng: %{message}" + embedded_table_loading: "Không thể tải chế độ xem được nhúng: %{message}" enumeration_activities: "Hoạt động (theo dõi thời gian)" enumeration_doc_categories: "Danh mục tài liệu" enumeration_work_package_priorities: "Độ ưu tiên của work package" filter: - more_values_not_shown: "Có %{total} kết quả nữa, tìm kiếm để lọc kết quả." + more_values_not_shown: "Còn %{total} kết quả khác, tìm kiếm để lọc kết quả." description: - text_open_filter: "Mở bộ lọc này với 'ALT' và phím mũi tên." - text_close_filter: "Để nhập thôi ô đang kích hoạt nội dung Vd ấn enter. Nếu không muốn lọc lựa chọn mục rỗng đầu tiên." + text_open_filter: "Mở bộ lọc này bằng 'ALT' và các phím mũi tên." + text_close_filter: "Ví dụ: để chọn một mục, hãy để lại tiêu điểm bằng cách nhấn enter. Để thoát mà không có bộ lọc, hãy chọn mục nhập đầu tiên (trống)." noneElement: "(không có)" time_zone_converted: two_values: "%{from} - %{to} theo giờ địa phương của bạn." only_start: "Từ %{from} theo giờ địa phương của bạn." - only_end: "Đến %{to} theo giờ địa phương của bạn." + only_end: "Cho tới %{to} theo giờ địa phương của bạn." value_spacer: "-" sorting: criteria: @@ -253,63 +254,63 @@ vi: two: "Tiêu chí phân loại thứ hai" three: "Tiêu chí phân loại thứ ba" gantt_chart: - label: "Biểu đồ Gantt" + label: "biểu đồ Gantt" quarter_label: "Q%{quarter_number}" labels: title: "Cấu hình nhãn" bar: "Nhãn thanh" - left: "Trái" - right: "Phải" - farRight: "Xa phải" + left: "trái" + right: "đúng" + farRight: "Ngoài cùng bên phải" description: > - Chọn các thuộc tính bạn muốn hiển thị ở các vị trí tương ứng trên biểu đồ Gantt mọi lúc. Lưu ý rằng khi di chuột qua một phần tử, nhãn ngày của nó sẽ được hiển thị thay vì các thuộc tính này. + Chọn các thuộc tính bạn muốn hiển thị ở các vị trí tương ứng của biểu đồ Gantt mọi lúc. Lưu ý rằng khi di chuột qua một phần tử, nhãn ngày của phần tử đó sẽ được hiển thị thay vì các thuộc tính này. button_activate: "Xem biểu đồ sự kiện" button_deactivate: "Ân biểu đồ sự kiện" filter: noneSelection: "(không có)" selection_mode: - notification: "Nhấp vào bất kỳ gói công việc nào được làm nổi bật để tạo mối quan hệ. Nhấn escape để hủy bỏ." + notification: "Bấm vào bất kỳ gói công việc được đánh dấu nào để tạo mối quan hệ. Nhấn thoát để hủy." zoom: in: "Phóng to" out: "Thu nhỏ" - auto: "Tự động phóng to" - days: "Ngày" - weeks: "Tuần" + auto: "Tự động thu phóng" + days: "ngày" + weeks: "tuần" months: "Tháng" - quarters: "Quý" - years: "Năm" + quarters: "Khu" + years: "năm" description: > - Chọn mức phóng to ban đầu sẽ được hiển thị khi phóng tự động không có sẵn. + Chọn mức thu phóng ban đầu sẽ được hiển thị khi không có tính năng tự động thu phóng. general_text_no: "không" - general_text_yes: "có" - general_text_No: "Không" + general_text_yes: "Có" + general_text_No: "không" general_text_Yes: "Có" hal: error: update_conflict_refresh: "Nhấn vào đây để làm mới tài nguyên và cập nhật lên phiên bản mới nhất." - edit_prohibited: "Chỉnh sửa%{attribute} bị chặn đối với tài nguyên này. Thuộc tính này có nguồn gốc từ các mối quan hệ (ví dụ: trẻ em) hoặc mặt khác không thể định cấu hình." + edit_prohibited: "Chỉnh sửa %{attribute} bị chặn đối với tài nguyên này. Thuộc tính này có nguồn gốc từ các mối quan hệ (ví dụ: con) hoặc không thể cấu hình được." format: - date: "%{attribute} không phải là ngày hợp lệ - phải là YYYY-MM-DD." + date: "%{attribute} không có ngày hợp lệ - dự kiến ​​là YYYY-MM-DD." general: "Lỗi đã xảy ra." ical_sharing_modal: title: "Đăng ký lịch" - inital_setup_error_message: "Đã xảy ra lỗi khi lấy dữ liệu." - description: "Bạn có thể sử dụng URL (iCalendar) để đăng ký lịch này trong một ứng dụng bên ngoài và xem thông tin gói công việc cập nhật từ đó." - warning: "Vui lòng không chia sẻ URL này với người khác. Bất kỳ ai có liên kết này đều có thể xem chi tiết gói công việc mà không cần tài khoản hoặc mật khẩu." + inital_setup_error_message: "Đã xảy ra lỗi khi tìm nạp dữ liệu." + description: "Bạn có thể sử dụng URL (iCalendar) để đăng ký lịch này trong ứng dụng khách bên ngoài và xem thông tin cập nhật về gói công việc từ đó." + warning: "Vui lòng không chia sẻ URL này với người dùng khác. Bất kỳ ai có liên kết này đều có thể xem chi tiết gói công việc mà không cần có tài khoản hoặc mật khẩu." token_name_label: "Bạn sẽ sử dụng cái này ở đâu?" token_name_placeholder: 'Nhập tên, ví dụ: "Điện thoại"' - token_name_description_text: 'If you subscribe to this calendar from multiple devices, this name will help you distinguish between them in your access tokens list.' + token_name_description_text: 'Nếu bạn đăng ký lịch này từ nhiều thiết bị, tên này sẽ giúp bạn phân biệt giữa chúng trong danh sách token truy cập của mình.' copy_url_label: "Sao chép URL" ical_generation_error_text: "Đã xảy ra lỗi khi tạo URL lịch." - success_message: 'URL "%{name}" đã được sao chép thành công vào clipboard của bạn. Dán nó vào ứng dụng lịch của bạn để hoàn tất việc đăng ký.' - label_activate: "Kích hoạt" - label_assignee: "Người được giao" - label_assignee_alt_text: "This work package is assigned to %{name}" + success_message: 'URL "%{name}" đã được sao chép thành công vào khay nhớ tạm của bạn. Dán nó vào ứng dụng lịch của bạn để hoàn tất đăng ký.' + label_activate: "kích hoạt" + label_assignee: "Người được chuyển nhượng" + label_assignee_alt_text: "Gói công việc này được giao cho %{name}" label_add_column_after: "Thêm cột phía sau" label_add_column_before: "Thêm cột phía trước" label_add_columns: "Thêm cột" - label_add_comment: "Thêm bình luận" - label_add_comment_title: "Thêm bình luận và gõ @ để thông báo cho người khác" + label_add_comment: "Thêm nhận xét" + label_add_comment_title: "Bình luận và gõ @ để thông báo cho người khác" label_add_row_after: "Thêm dòng bên dưới" label_add_row_before: "Thêm dòng bên trên" label_add_selected_columns: "Thêm cột đang chọn" @@ -318,84 +319,84 @@ vi: label_ago: "vài ngày trước" label_all: "tất cả" label_all_projects: "Tất cả dự án" - label_all_uppercase: "Tất cả" + label_all_uppercase: "tất cả" label_all_work_packages: "Tất cả công việc" label_and: "và" - label_ascending: "Tăng dần" + label_ascending: "tăng dần" label_author: "Tác giả: %{user}" - label_avatar: "Ảnh đại diện" + label_avatar: "hình đại diện" label_between: "giữa" - label_board: "Bảng" - label_board_locked: "Đã khóa" - label_board_plural: "Bảng" - label_board_sticky: "Chú ý" - label_change: "Thay đổi" + label_board: "bảng" + label_board_locked: "bị khóa" + label_board_plural: "bảng" + label_board_sticky: "dính" + label_change: "thay đổi" label_create: "Tạo mới" label_create_work_package: "Tạo gói công việc mới" - label_created_by: "Được tạo bởi" + label_created_by: "Tạo bởi" label_current: "hiện tại" - label_date: "Ngày" - label_date_with_format: "Nhập vào %{date_attribute} bằng cách sử dụng định dạng sau: %{format}" - label_deactivate: "Tắt" + label_date: "ngày" + label_date_with_format: "Nhập %{date_attribute} bằng định dạng sau: %{format}" + label_deactivate: "Vô hiệu hóa" label_descending: "Giảm dần" - label_description: "Mô tả" - label_details: "Chi tiết" - label_display: "Hiển thị" - label_cancel_comment: "Hủy bình luận" - label_closed_work_packages: "đã đóng" + label_description: "mô tả" + label_details: "chi tiết" + label_display: "trưng bày" + label_cancel_comment: "Hủy nhận xét" + label_closed_work_packages: "đóng cửa" label_collapse: "Thu gọn" label_collapsed: "đóng" label_collapse_all: "Thu gọn tất cả" - label_collapse_text: "Collapse text" - label_comment: "Nhận xét" - label_committed_at: "%{committed_revision_link} lúc %{date}" - label_committed_link: "phiên bản commit %{revision_identifier}" + label_collapse_text: "Thu gọn văn bản" + label_comment: "bình luận" + label_committed_at: "%{committed_revision_link} tại %{date}" + label_committed_link: "cam kết sửa đổi %{revision_identifier}" label_contains: "bao gồm" - label_created_on: "tạo ngày" - label_edit_comment: "Chỉnh sửa chú thích này" + label_created_on: "được tạo trên" + label_edit_comment: "Chỉnh sửa nhận xét này" label_edit_status: "Trạng thái chỉnh sửa của nhóm công việc" - label_email: "Thư điện tử" + label_email: "email" label_equals: "là" label_expand: "Mở rộng" - label_expanded: "đã mở rộng" + label_expanded: "mở rộng" label_expand_all: "Mở rộng tất cả" - label_expand_text: "Show full text" + label_expand_text: "Hiển thị toàn văn" label_expand_project_menu: "Mở rộng menu dự án" - label_export: "Xuất" - label_export_preparing: "Quá trình xuất đang được chuẩn bị và sẽ được tải xuống ngay." - label_favorites: "Yêu thích" - label_filename: "Tệp" - label_filesize: "Kích cỡ" - label_general: "Tổng quan" + label_export: "xuất khẩu" + label_export_preparing: "Quá trình xuất đang được chuẩn bị và sẽ sớm được tải xuống." + label_favorites: "yêu thích" + label_filename: "tập tin" + label_filesize: "kích thước" + label_general: "chung" label_greater_or_equal: ">=" - label_group: "Nhóm" + label_group: "nhóm" label_group_by: "Nhóm theo" - label_group_plural: "Các Nhóm" + label_group_plural: "nhóm" label_hide_attributes: "Hiện ít hơn" label_hide_column: "Ẩn cột" label_hide_project_menu: "Thu gọn menu dự án" label_in: "trong" label_in_less_than: "ít hơn" - label_in_more_than: "nhiều hơn" + label_in_more_than: "trong hơn" label_incoming_emails: "Các thư đến" - label_information_plural: "Thông tin" - label_invalid: "Không hợp lệ" - label_import: "Nhập khẩu" + label_information_plural: "thông tin" + label_invalid: "không hợp lệ" + label_import: "nhập khẩu" label_latest_activity: "Hoạt động mới nhất" label_last_updated_on: "Cập nhật lần cuối lúc" label_learn_more_link: "Tìm hiểu thêm" label_less_or_equal: "<=" - label_less_than_ago: "ít hơn ngày trước" + label_less_than_ago: "chưa đầy ngày trước" label_loading: "Đang tải..." label_mail_notification: "Thông báo qua email" label_manage_columns: "Quản lý và sắp xếp lại các cột" label_me: "tôi" - label_meeting_agenda: "Chương trình nghị sự" - label_meeting_minutes: "Biên bản cuộc họp" + label_meeting_agenda: "chương trình họp" + label_meeting_minutes: "phút" label_more_than_ago: "nhiều hơn mấy ngày trước" - label_moderate_comment: "Moderate comment" + label_moderate_comment: "Kiểm duyệt bình luận" label_next: "Tiếp" - label_no_color: "Không màu" + label_no_color: "không có màu sắc" label_no_data: "Không có dữ liệu để hiển thị" label_no_due_date: "không có ngày kết thúc" label_no_start_date: "không có ngày bắt đầu" @@ -403,18 +404,18 @@ vi: label_no_value: "Không có giá trị" label_none: "không" label_not_contains: "không chứa" - label_not_equals: "không là" - label_on: "lúc" + label_not_equals: "không phải" + label_on: "trên" label_open_menu: "Mở trình đơn" label_open_context_menu: "Mở menu ngữ cảnh" label_open_work_packages: "mở" label_password: "Mật khẩu" - label_previous: "Trước đó" + label_previous: "trước đó" label_per_page: "Mỗi trang:" - label_please_wait: "Vui lòng chờ" - label_project: "Dự án" + label_please_wait: "Vui lòng chờ" + label_project: "dự án" label_project_list: "Danh sách dự án" - label_project_plural: "Các dự án" + label_project_plural: "dự án" label_visibility_settings: "Cài đặt hiển thị" label_quote_comment: "Trích bình luận này" label_recent: "Gần đây" @@ -424,167 +425,167 @@ vi: label_remove_columns: "Loại bỏ cột được chọn" label_remove_row: "Xóa hàng" label_report: "Báo cáo" - label_repository_plural: "Kho lưu trữ" - label_resize_project_menu: "Resize project menu" + label_repository_plural: "kho lưu trữ" + label_resize_project_menu: "Thay đổi kích thước menu dự án" label_save_as: "Lưu thành" - label_search_columns: "Tìm cột" + label_search_columns: "Tìm kiếm một cột" label_select_watcher: "Chọn một người theo dõi..." - label_selected_filter_list: "Các bộ lọc đã chọn: %s" + label_selected_filter_list: "Bộ lọc đã chọn" label_show_attributes: "Hiển thị tất cả các thuộc tính" label_show_in_menu: "Hiển thị chế độ xem trong menu" label_sort_by: "Sắp xếp theo" label_sorted_by: "sắp xếp theo" label_sort_higher: "Di chuyển lên" label_sort_lower: "Dịch xuống" - label_sorting: "Sắp xếp" - label_spent_time: "Thời gian" - label_star_query: "Favorited" + label_sorting: "sắp xếp" + label_spent_time: "dành thời gian" + label_star_query: "được yêu thích" label_press_enter_to_save: "Nhấn enter để lưu." - label_public_query: "Công cộng" - label_sum: "Tổng" - label_sum_for: "Tổng cho" - label_total_sum: "Tổng cộng" + label_public_query: "công khai" + label_sum: "tổng hợp" + label_sum_for: "Tổng hợp cho" + label_total_sum: "Tổng số tiền" label_subject: "Chủ đề" label_this_week: "tuần này" - label_today: "Hôm nay" - label_time_entry_plural: "Thời gian" - label_up: "Lên" - label_user_plural: "Người dùng" - label_activity_show_only_comments: "Hiển thị các hoạt động với các bình luận" + label_today: "hôm nay" + label_time_entry_plural: "dành thời gian" + label_up: "lên" + label_user_plural: "người dùng" + label_activity_show_only_comments: "Chỉ hiển thị các hoạt động có nhận xét" label_activity_show_all: "Hiển thị các hoạt động" label_total_progress: "%{percent}% Tổng tiến độ" - label_total_amount: "Tổng số tiền%{amount}" - label_updated_on: "cập Nhật ngày" - label_value_derived_from_children: "(giá trị lấy từ con)" - label_children_derived_duration: "Thời gian gói công việc được lấy từ các con" + label_total_amount: "Tổng cộng: %{amount}" + label_updated_on: "cập nhật trên" + label_value_derived_from_children: "(giá trị bắt nguồn từ trẻ em)" + label_children_derived_duration: "Thời lượng bắt nguồn của gói công việc con" label_warning: "Cảnh báo" label_work_package: "Work Package" - label_work_package_parent: "Gói công việc cha" - label_work_package_plural: "Work Packages" - label_watch: "Theo dõi" + label_work_package_parent: "Gói công việc của phụ huynh" + label_work_package_plural: "Gói công việc" + label_watch: "xem" label_watch_work_package: "Xem gói công việc" label_watcher_added_successfully: "Đã thêm nhận xét thành công." label_watcher_deleted_successfully: "Người theo dõi đã được xóa thành công!" label_work_package_details_you_are_here: "Bạn đang ở tab %{tab} cho %{type} %{subject}." label_work_package_context_menu: "Menu ngữ cảnh gói công việc" - label_unwatch: "Ngừng theo dõi" - label_unwatch_work_package: "Ngừng theo dõi gói công việc" + label_unwatch: "Bỏ xem" + label_unwatch_work_package: "Bỏ xem gói công việc" label_uploaded_by: "Tải lên bởi" - label_default_queries: "Mặc định" - label_starred_queries: "Yêu thích" - label_global_queries: "Công cộng" - label_custom_queries: "Riêng tư" + label_default_queries: "mặc định" + label_starred_queries: "yêu thích" + label_global_queries: "công khai" + label_custom_queries: "riêng tư" label_columns: "Cột" - label_attachments: Đính kèm - label_drop_files: "Kéo các tệp vào đây để đính kèm." - label_drop_or_click_files: "Kéo các tệp vào đây hoặc nhấp để đính kèm." - label_drop_folders_hint: Bạn không thể tải lên thư mục dưới dạng tài liệu đính kèm. Vui lòng chọn các tệp đơn lẻ. - label_add_attachments: "Đính kèm tệp" - label_formattable_attachment_hint: "Đính kèm và liên kết tập tin bằng cách thả vào đây, hoặc copy và dán." + label_attachments: tệp đính kèm + label_drop_files: "Thả tập tin vào đây để đính kèm tập tin." + label_drop_or_click_files: "Thả tập tin vào đây hoặc bấm vào để đính kèm tập tin." + label_drop_folders_hint: Bạn không thể tải lên các thư mục dưới dạng tệp đính kèm. Vui lòng chọn các tập tin duy nhất. + label_add_attachments: "Đính kèm tập tin" + label_formattable_attachment_hint: "Đính kèm và liên kết các tệp bằng cách thả vào trường này hoặc dán từ bảng ghi tạm." label_remove_file: "Xóa %{fileName}" label_remove_watcher: "Loại bỏ người theo dõi %{name}" label_remove_all_files: Xóa tất cả các tập tin label_add_description: "Thêm mô tả cho %{file}" - label_upload_notification: "Tải lên tệp tin..." - label_work_package_upload_notification: "Tải file cho một công việc #%{id}: %{subject}" + label_upload_notification: "Đang tải tệp lên..." + label_work_package_upload_notification: "Đang tải lên tập tin cho gói công việc #%{id}: %{subject}" label_wp_id_added_by: "#%{id} được thêm bởi %{author}" label_files_to_upload: "Những tập tin này sẽ được tải lên:" - label_rejected_files: "Các tệp này không thể được tải lên:" - label_rejected_files_reason: "Các tệp này không thể được tải lên như kích thước của họ là lớn hơn %{maximumFilesize}" - label_wait: "Xin vui lòng chờ cho cấu hình..." - label_upload_counter: "%{done} %{count} tập tin hoàn thành" - label_validation_error: "Không thể lưu các công việc do các lỗi sau đây:" - label_version_plural: "Các phiên bản" - label_view_has_changed: "Chế độ xem này có các thay đổi chưa được lưu. Nhấp để lưu chúng." + label_rejected_files: "Những tập tin này không thể được tải lên:" + label_rejected_files_reason: "Không thể tải lên những tệp này vì kích thước của chúng lớn hơn %{maximumFilesize}" + label_wait: "Vui lòng chờ cấu hình..." + label_upload_counter: "%{done} trong số %{count} tệp đã hoàn tất" + label_validation_error: "Không thể lưu gói công việc do các lỗi sau:" + label_version_plural: "phiên bản" + label_view_has_changed: "Chế độ xem này có những thay đổi chưa được lưu. Nhấn vào đây để lưu chúng." help_texts: - show_modal: "Show help text" + show_modal: "Hiển thị văn bản trợ giúp" onboarding: buttons: - skip: "Bỏ qua" + skip: "bỏ qua" next: "Tiếp" - got_it: "Đã hiểu" + got_it: "Hiểu rồi" steps: help_menu: "Menu Trợ giúp (?) cung cấp các tài nguyên trợ giúp bổ sung. Tại đây bạn có thể tìm thấy hướng dẫn người dùng, video hướng dẫn hữu ích và nhiều hơn nữa.
    Chúc bạn làm việc vui vẻ với OpenProject!" - members: "Mời thành viên mới tham gia dự án của bạn." - quick_add_button: "Nhấp vào biểu tượng cộng (+) trong thanh điều hướng tiêu đề để tạo một dự án mới hoặc mời đồng nghiệp." - sidebar_arrow: "Sử dụng mũi tên trở lại ở góc trên bên trái để quay lại menu chính của dự án." - 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." - wiki: "Trong wiki, bạn có thể tài liệu hóa và chia sẻ kiến thức cùng với nhóm của bạn." + members: "Mời thành viên mới tham gia dự án của bạn." + quick_add_button: "Nhấp vào biểu tượng dấu cộng (+) trong điều hướng tiêu đề để tạo dự án mới hoặc để mời đồng nghiệp." + sidebar_arrow: "Sử dụng mũi tên quay lại ở góc trên cùng bên trái để quay lại menu chính của dự án." + welcome: "Hãy tham gia chuyến tham quan giới thiệu dài ba phút để tìm hiểu tính năng quan trọng nhất.
    Chúng tôi khuyên bạn nên hoàn thành các bước cho đến hết. Bạn có thể bắt đầu lại chuyến tham quan bất cứ lúc nào." + wiki: "Trong wiki bạn có thể ghi lại và chia sẻ kiến ​​thức cùng với nhóm của mình." backlogs: overview: "Quản lý công việc của bạn trong chế độ xem backlogs." - sprints: "Bên phải bạn có backlog sản phẩm và backlog lỗi, bên trái bạn có các sprint tương ứng. Tại đây bạn có thể tạo epics, user stories và bugs, ưu tiên qua kéo & thả và thêm chúng vào một sprint." - task_board_arrow: "Để xem bảng công việc của bạn, mở menu thả xuống sprint..." - task_board_select: "...và chọn mục bảng công việc." - task_board: "Bảng công việc hình ảnh hóa tiến độ cho sprint này. Nhấp vào biểu tượng cộng (+) bên cạnh một user story để thêm nhiệm vụ hoặc vấn đề.
    Trạng thái có thể được cập nhật bằng cách kéo và thả." + sprints: "Ở bên phải, bạn có tồn đọng sản phẩm và tồn đọng lỗi, ở bên trái, bạn có các lần chạy nước rút tương ứng. Tại đây, bạn có thể tạo sử thi, cốt truyện của người dùng và lỗi, ưu tiên bằng cách kéo và thả và thêm chúng vào chạy nước rút." + task_board_arrow: "Để xem bảng nhiệm vụ của bạn, hãy mở trình đơn thả xuống chạy nước rút..." + task_board_select: "...và chọn mục nhập bảng nhiệm vụ." + task_board: "Bảng nhiệm vụ hiển thị tiến trình cho lần chạy nước rút này. Nhấp vào biểu tượng dấu cộng (+) bên cạnh cốt truyện của người dùng để thêm nhiệm vụ hoặc trở ngại mới.
    Trạng thái có thể được cập nhật bằng cách kéo và thả." boards: - overview: "Chọn bảng để thay đổi chế độ xem và quản lý dự án của bạn bằng chế độ xem bảng agile." - lists_kanban: "Tại đây bạn có thể tạo nhiều danh sách (cột) trong bảng của bạn. Tính năng này cho phép bạn tạo một bảng Kanban, ví dụ." - lists_basic: "Tại đây bạn có thể tạo nhiều danh sách (cột) trong bảng agile của bạn." - add: "Nhấp vào biểu tượng cộng (+) để tạo một thẻ mới hoặc thêm một thẻ hiện có vào danh sách trên bảng." - drag: "Kéo và thả các thẻ của bạn trong một danh sách để thay đổi thứ tự, hoặc di chuyển chúng đến danh sách khác.
    Bạn có thể nhấp vào biểu tượng thông tin (i) ở góc trên bên phải hoặc nhấp đúp vào thẻ để mở chi tiết của nó." + overview: "Chọn boards để thay đổi chế độ xem và quản lý dự án của bạn bằng chế độ xem bảng linh hoạt." + lists_kanban: "Tại đây bạn có thể tạo nhiều danh sách (cột) trong bảng của mình. Ví dụ: tính năng này cho phép bạn tạo Kanban board." + lists_basic: "Tại đây, bạn có thể tạo nhiều danh sách (cột) trong bảng linh hoạt của mình." + add: "Bấm vào biểu tượng dấu cộng (+) để tạo thẻ mới hoặc thêm thẻ hiện có vào danh sách trên bảng." + drag: "Kéo và thả thẻ của bạn trong danh sách nhất định để sắp xếp lại chúng hoặc di chuyển chúng sang danh sách khác.
    Bạn có thể nhấp vào biểu tượng thông tin (i) ở góc trên bên phải hoặc nhấp đúp vào thẻ để mở thông tin chi tiết." wp: - toggler: "Bây giờ hãy xem phần gói công việc, nơi cung cấp cái nhìn chi tiết hơn về công việc của bạn." - list: "Danh sách gói công việc này cung cấp danh sách tất cả công việc trong dự án của bạn, chẳng hạn như nhiệm vụ, mốc, giai đoạn, và nhiều hơn nữa.
    Các gói công việc có thể được tạo và chỉnh sửa trực tiếp từ chế độ xem này. Để truy cập chi tiết của một gói công việc cụ thể, chỉ cần nhấp đúp vào hàng của nó." - full_view: "Chế độ xem chi tiết gói công việc cung cấp tất cả thông tin liên quan đến một gói công việc nhất định, chẳng hạn như mô tả, trạng thái, ưu tiên, hoạt động, phụ thuộc và bình luận." - back_button: "Sử dụng mũi tên trở lại ở góc trên bên trái để thoát và quay lại danh sách gói công việc." - create_button: "Nút + Tạo sẽ thêm một gói công việc mới vào dự án của bạn." - gantt_menu: "Tạo lịch dự án và thời gian một cách dễ dàng bằng cách sử dụng mô-đun biểu đồ Gantt." - timeline: "Tại đây bạn có thể chỉnh sửa kế hoạch dự án, tạo các gói công việc mới, chẳng hạn như nhiệm vụ, mốc, giai đoạn, và nhiều hơn nữa, cũng như thêm phụ thuộc. Tất cả các thành viên trong nhóm có thể xem và cập nhật kế hoạch mới nhất bất kỳ lúc nào." + toggler: "Bây giờ chúng ta hãy xem phần work package, phần này sẽ cung cấp cho bạn cái nhìn chi tiết hơn về công việc của bạn." + list: "Tổng quan về gói work này cung cấp danh sách tất cả công việc trong dự án của bạn, chẳng hạn như nhiệm vụ, cột mốc, giai đoạn, v.v.
    Các gói công việc có thể được tạo và chỉnh sửa trực tiếp từ chế độ xem này. Để truy cập chi tiết của một gói công việc cụ thể, chỉ cần nhấp đúp vào hàng của nó." + full_view: "Chế độ xem chi tiết gói công việc cung cấp tất cả thông tin liên quan liên quan đến gói công việc nhất định, chẳng hạn như mô tả, trạng thái, mức độ ưu tiên, hoạt động, sự phụ thuộc và nhận xét." + back_button: "Sử dụng mũi tên quay lại ở góc trên cùng bên trái để thoát và quay lại danh sách gói công việc." + create_button: "Nút + Create sẽ thêm gói công việc mới vào dự án của bạn." + gantt_menu: "Tạo lịch trình và mốc thời gian của dự án một cách dễ dàng bằng cách sử dụng mô-đun biểu đồ Gantt." + timeline: "Tại đây, bạn có thể chỉnh sửa kế hoạch dự án của mình, tạo các gói công việc mới, chẳng hạn như nhiệm vụ, mốc quan trọng, giai đoạn, v.v., cũng như thêm các phần phụ thuộc. Tất cả thành viên trong nhóm có thể xem và cập nhật kế hoạch mới nhất bất cứ lúc nào." team_planner: - overview: "Lập kế hoạch đội ngũ cho phép bạn phân công nhiệm vụ cho các thành viên trong nhóm và có cái nhìn tổng quan về ai đang làm gì." - calendar: "Bảng kế hoạch hàng tuần hoặc hai tuần hiển thị tất cả các gói công việc được phân công cho các thành viên trong nhóm của bạn." - add_assignee: "Để bắt đầu, thêm người thực hiện vào lập kế hoạch đội ngũ." - add_existing: "Tìm các gói công việc hiện có và kéo chúng vào lập kế hoạch đội ngũ để ngay lập tức phân công cho một thành viên trong nhóm và xác định ngày bắt đầu và kết thúc." - card: "Kéo các gói công việc theo chiều ngang để di chuyển chúng lùi hoặc tiến về phía trước theo thời gian, kéo các cạnh để thay đổi ngày bắt đầu và kết thúc và thậm chí kéo chúng theo chiều dọc đến hàng khác để phân công cho một thành viên khác." + overview: "Công cụ lập kế hoạch nhóm cho phép bạn phân công nhiệm vụ cho các thành viên trong nhóm một cách trực quan và có được cái nhìn tổng quan về ai đang làm việc gì." + calendar: "Bảng kế hoạch hàng tuần hoặc hai tuần một lần hiển thị tất cả các gói công việc được giao cho các thành viên trong nhóm của bạn." + add_assignee: "Để bắt đầu, hãy thêm người được phân công vào người lập kế hoạch nhóm." + add_existing: "Tìm kiếm các gói công việc hiện có và kéo chúng vào bảng lập kế hoạch nhóm để phân công ngay cho một thành viên trong nhóm cũng như xác định ngày bắt đầu và ngày kết thúc." + card: "Kéo các gói công việc theo chiều ngang để di chuyển chúng tới hoặc lui theo thời gian, kéo các cạnh để thay đổi ngày bắt đầu và ngày kết thúc, thậm chí kéo chúng theo chiều dọc đến một hàng khác để gán chúng cho thành viên khác." notifications: - title: "Thông báo" + title: "thông báo" no_unread: "Không có thông báo chưa đọc" reasons: - mentioned: "Nhắc đến" + mentioned: "Được đề cập" watched: "Người quan sát" - assigned: "Người được giao" + assigned: "Người được chuyển nhượng" #The enum value is named 'responsible' in the database and that is what is transported through the API #up to the frontend. - responsible: "Trách nhiệm" - created: "Đã tạo" - scheduled: "Đã xếp lịch" - commented: "Nhận xét" + responsible: "chịu trách nhiệm" + created: "đã tạo" + scheduled: "theo lịch trình" + commented: "Đã bình luận" processed: "đã xử lý" - prioritized: "được ưu tiên" + prioritized: "Được ưu tiên" dateAlert: "Cảnh báo ngày" - shared: "Được chia sẻ" - reminder: "Reminder" + shared: "đã chia sẻ" + reminder: "Lời nhắc" date_alerts: - milestone_date: "Ngày mốc" - overdue: "Quá hạn" - overdue_since: "for %{difference_in_days}." - property_today: "is today." - property_is: "is in %{difference_in_days}." - property_was: "was %{difference_in_days} ago." - property_is_deleted: "is deleted." + milestone_date: "Ngày quan trọng" + overdue: "quá hạn" + overdue_since: "cho %{difference_in_days}." + property_today: "là ngày hôm nay." + property_is: "ở %{difference_in_days}." + property_was: "là %{difference_in_days} trước đây." + property_is_deleted: "bị xóa." center: label_actor_and: "và" and_more_users: - other: "và %{count} nữa" + other: "và %{count} người khác" no_results: - at_all: "Thông báo mới sẽ xuất hiện ở đây khi có hoạt động liên quan đến bạn." - with_current_filter: "Hiện không có thông báo trong chế độ xem này" + at_all: "Thông báo mới sẽ xuất hiện ở đây khi có hoạt động khiến bạn quan tâm." + with_current_filter: "Hiện tại không có thông báo nào trong chế độ xem này" mark_all_read: "Đánh dấu tất cả là đã đọc" mark_as_read: "Đánh dấu là đã đọc" - mark_all_read_confirmation: "This will mark all notifications in this view as read. Are you sure you want to do this?" + mark_all_read_confirmation: "Thao tác này sẽ đánh dấu tất cả thông báo trong chế độ xem này là đã đọc. Bạn có chắc chắn muốn làm điều này?" text_update_date_by: "%{date} bởi" - total_count_warning: "Hiển thị %{newest_count} thông báo gần đây nhất. %{more_count} thông báo khác chưa được hiển thị." + total_count_warning: "Hiển thị %{newest_count} thông báo gần đây nhất. %{more_count} thêm không được hiển thị." empty_state: - no_notification: "Có vẻ như bạn đã đọc hết các thông báo." - no_notification_with_current_project_filter: "Có vẻ như bạn đã đọc hết các thông báo của dự án đã chọn." - no_notification_with_current_filter: "Có vẻ như bạn đã đọc hết thông báo cho bộ lọc %{filter}." - no_notification_for_filter: "Có vẻ như bạn đã hiểu hết về bộ lọc này." + no_notification: "Có vẻ như tất cả các bạn đã bị cuốn theo." + no_notification_with_current_project_filter: "Có vẻ như tất cả các bạn đã bắt kịp dự án đã chọn." + no_notification_with_current_filter: "Có vẻ như bạn đã quan tâm đến bộ lọc %{filter}." + no_notification_for_filter: "Có vẻ như tất cả các bạn đều bị cuốn hút bởi bộ lọc này." no_selection: "Nhấp vào thông báo để xem tất cả chi tiết hoạt động." new_notifications: message: "Có thông báo mới." - link_text: "Nhấp vào đây để tải chúng." + link_text: "Nhấn vào đây để tải chúng." reminders: - note: "Note: “%{note}”" + note: "Lưu ý: “%{note}”" settings: change_notification_settings: 'Bạn có thể thay đổi cài đặt thông báo của mình để đảm bảo không bỏ lỡ cập nhật quan trọng nào.' title: "Cài đặt thông báo" @@ -599,266 +600,266 @@ vi: P7D: một tuần trước overdue: P1D: mỗi ngày - P3D: mỗi 3 ngày + P3D: cứ sau 3 ngày P7D: mỗi tuần reasons: mentioned: - title: "nhắc đến" - description: "Nhận thông báo mỗi khi ai đó nhắc đến tôi ở bất kỳ đâu" - assignee: "Người được giao" - responsible: "Trách nhiệm" - shared: "được chia sẻ" - watched: "Người quan sát" + title: "Được đề cập" + description: "Nhận thông báo mỗi khi ai đó nhắc đến tôi ở bất cứ đâu" + assignee: "Người được chuyển nhượng" + responsible: "chịu trách nhiệm" + shared: "đã chia sẻ" + watched: "người theo dõi" work_package_commented: "Tất cả bình luận mới" work_package_created: "Gói công việc mới" - work_package_processed: "Tất cả thay đổi trạng thái" - work_package_prioritized: "Tất cả thay đổi ưu tiên" - work_package_scheduled: "Tất cả thay đổi ngày" + work_package_processed: "Mọi thay đổi trạng thái" + work_package_prioritized: "Tất cả các thay đổi ưu tiên" + work_package_scheduled: "Tất cả các thay đổi ngày" global: immediately: title: "Tham gia" - description: "Thông báo cho tất cả hoạt động trong các gói công việc mà bạn tham gia (người được giao, người chịu trách nhiệm hoặc người theo dõi)." + description: "Thông báo cho tất cả các hoạt động trong gói công việc mà bạn tham gia (người được giao, người chịu trách nhiệm hoặc người theo dõi)." delayed: title: "Không tham gia" description: "Thông báo bổ sung cho các hoạt động trong tất cả các dự án." date_alerts: - title: "Cảnh báo ngày" - description: "Thông báo tự động khi các ngày quan trọng sắp đến cho các gói công việc mở mà bạn tham gia (người được giao, người chịu trách nhiệm hoặc người theo dõi)." + title: "Thông báo ngày" + description: "Thông báo tự động khi các ngày quan trọng đang đến gần đối với các gói công việc đang mở mà bạn tham gia (người được giao, người chịu trách nhiệm hoặc người theo dõi)." overdue: Khi quá hạn project_specific: - title: "Cài đặt thông báo theo dự án" - description: "Các cài đặt theo dự án này ghi đè các cài đặt mặc định ở trên." + title: "Cài đặt thông báo dành riêng cho dự án" + description: "Các cài đặt dành riêng cho dự án này sẽ ghi đè các cài đặt mặc định ở trên." add: "Thêm cài đặt cho dự án" already_selected: "Dự án này đã được chọn" remove: "Xóa cài đặt dự án" pagination: no_other_page: "Bạn đang trên trang duy nhất." - pages_skipped: "Pages skipped." - page_navigation: "Pagination navigation" - per_page_navigation: 'Items per page selection' + pages_skipped: "Các trang bị bỏ qua." + page_navigation: "Điều hướng phân trang" + per_page_navigation: 'Lựa chọn mục trên mỗi trang' pages: - page_number: Page %{number} - show_per_page: Show %{number} per page + page_number: Trang %{number} + show_per_page: Hiển thị %{number} trên mỗi trang placeholders: default: "-" - subject: "Nhập tiêu đề ở đây" + subject: "Nhập chủ đề vào đây" selection: "Vui lòng chọn" - description: "Mô tả: Nhấp để chỉnh sửa..." + description: "Mô tả: Click để chỉnh sửa..." relation_description: "Nhấp để thêm mô tả cho mối quan hệ này" project: autocompleter: - label: "Hoàn tất tự động dự án" + label: "Tự động hoàn thành dự án" click_to_switch_to_project: "Dự án: %{projectname}" - context: "Ngữ cảnh dự án" - not_available: "Project N/A" + context: "Bối cảnh dự án" + not_available: "Dự án N/A" required_outside_context: > - Vui lòng chọn một dự án để tạo gói công việc nhằm xem tất cả các thuộc tính. Bạn chỉ có thể chọn các dự án mà loại trên đã được kích hoạt. + Vui lòng chọn một dự án để tạo gói công việc để xem tất cả các thuộc tính. Bạn chỉ có thể chọn các dự án đã kích hoạt loại trên. reminders: settings: daily: add_time: "Thêm thời gian" - enable: "Kích hoạt nhắc nhở email hàng ngày" - explanation: "Bạn sẽ nhận được các nhắc nhở này chỉ cho các thông báo chưa đọc và chỉ vào những giờ bạn chỉ định. %{no_time_zone}" - no_time_zone: "Cho đến khi bạn cấu hình múi giờ cho tài khoản của mình, các giờ sẽ được hiểu là UTC." + enable: "Bật lời nhắc email hàng ngày" + explanation: "Bạn sẽ chỉ nhận được những lời nhắc này đối với những thông báo chưa đọc và chỉ vào những giờ bạn chỉ định. %{no_time_zone}" + no_time_zone: "Cho đến khi bạn định cấu hình múi giờ cho tài khoản của mình, thời gian sẽ được hiểu là theo UTC." time_label: "Thời gian %{counter}:" - title: "Gửi cho tôi nhắc nhở email hàng ngày cho các thông báo chưa đọc" + title: "Gửi cho tôi lời nhắc email hàng ngày về các thông báo chưa đọc" workdays: - title: "Nhận nhắc nhở email vào những ngày này" + title: "Nhận lời nhắc qua email vào những ngày này" immediate: - title: "Gửi cho tôi nhắc nhở email" - mentioned: "Ngay lập tức khi ai đó nhắc đến tôi" - personal_reminder: "Immediately when I receive a personal reminder" + title: "Gửi cho tôi lời nhắc qua email" + mentioned: "Ngay lập tức khi ai đó @đề cập đến tôi" + personal_reminder: "Ngay lập tức khi tôi nhận được lời nhắc cá nhân" alerts: - title: "Cảnh báo email cho các mục khác (không phải gói công việc)" + title: "Thông báo qua email cho các mục khác (không phải là gói công việc)" explanation: > - Các thông báo hiện tại chỉ giới hạn cho gói công việc. Bạn có thể chọn tiếp tục nhận cảnh báo email cho các sự kiện này cho đến khi chúng được đưa vào thông báo: - news_added: "Đã thêm tin" + Thông báo ngày hôm nay được giới hạn trong các gói công việc. Bạn có thể chọn tiếp tục nhận thông báo qua email cho những sự kiện này cho đến khi chúng được đưa vào thông báo: + news_added: "Đã thêm tin tức" news_commented: "Bình luận về một tin tức" - document_added: "Tài liệu được thêm vào" - forum_messages: "Tin nhắn diễn đàn mới" + document_added: "Đã thêm tài liệu" + forum_messages: "Thông báo diễn đàn mới" wiki_page_added: "Đã thêm trang Wiki" - wiki_page_updated: "Trang Wiki đã được cập nhật" - membership_added: "Thành viên được thêm vào" - membership_updated: "Thành viên được cập nhật" - title: "Nhắc nhở email" + wiki_page_updated: "Trang Wiki được cập nhật" + membership_added: "Đã thêm thành viên" + membership_updated: "Đã cập nhật tư cách thành viên" + title: "Lời nhắc qua email" pause: label: "Tạm dừng nhắc nhở email hàng ngày" first_day: "Ngày đầu tiên" last_day: "Ngày cuối cùng" text_are_you_sure: "Bạn có chắc không?" - text_are_you_sure_to_cancel: "You have unsaved changes on this page. Are you sure you want to discard them?" - breadcrumb: "Breadcrumb" + text_are_you_sure_to_cancel: "Bạn có những thay đổi chưa được lưu trên trang này. Bạn có chắc chắn muốn loại bỏ chúng không?" + breadcrumb: "vụn bánh mì" text_data_lost: "Tất cả dữ liệu đã nhập sẽ bị mất." text_user_wrote: "%{value} đã viết:" types: attribute_groups: - error_duplicate_group_name: "Tên %{group} được sử dụng nhiều hơn một lần. Tên nhóm phải là duy nhất." - error_no_table_configured: "Vui lòng cấu hình một bảng cho %{group}." + error_duplicate_group_name: "Tên %{group} được sử dụng nhiều lần. Tên nhóm phải là duy nhất." + error_no_table_configured: "Vui lòng định cấu hình bảng cho %{group}." reset_title: "Đặt lại cấu hình biểu mẫu" confirm_reset: > - Chú ý: Bạn có chắc bạn muốn đặt lại cấu hình dạng? Điều này sẽ thiết lập lại các thuộc tính của nhóm mặc định và vô hiệu hóa tất cả các trường tùy chỉnh. - upgrade_to_ee: "Nâng cấp lên phiên bản Enterprise on-premises" - upgrade_to_ee_text: "Wow! Nếu bạn cần tiện ích bổ sung này, bạn là một chuyên gia tuyệt vời! Bạn có thể hỗ trợ chúng tôi, các nhà phát triển OpenSource, bằng cách trở thành khách hàng của phiên bản Enterprise không?" + Cảnh báo: Bạn có chắc chắn muốn đặt lại cấu hình biểu mẫu không? Điều này sẽ đặt lại các thuộc tính về nhóm mặc định của chúng và vô hiệu hóa TẤT CẢ các trường tùy chỉnh. + upgrade_to_ee: "Nâng cấp lên phiên bản Enterprise tại chỗ" + upgrade_to_ee_text: "Ồ! Nếu bạn cần tiện ích bổ sung này thì bạn là một siêu chuyên nghiệp! Bạn có phiền khi hỗ trợ các nhà phát triển OpenSource của chúng tôi bằng cách trở thành khách hàng phiên bản Enterprise không?" more_information: "Thêm thông tin" - nevermind: "Không sao" + nevermind: "bỏ qua" time_entry: - work_package_required: "Yều cầu chọn một gói công việc đầu tiên." - title: "Thời gian truy cập" + work_package_required: "Yêu cầu chọn gói công việc trước tiên." + title: "Đăng nhập thời gian" tracking: "Theo dõi thời gian" stop: "Dừng lại" timer: - start_new_timer: "Bắt đầu đồng hồ bấm giờ mới" - timer_already_running: "Để bắt đầu đồng hồ bấm giờ mới, trước tiên bạn phải dừng đồng hồ bấm giờ hiện tại:" - timer_already_stopped: "Không có đồng hồ bấm giờ hoạt động cho gói công việc này, bạn đã dừng nó ở cửa sổ khác chưa?" - tracking_time: "Theo dõi thời gian" - button_stop: "Dừng đồng hồ bấm giờ hiện tại" + start_new_timer: "Bắt đầu hẹn giờ mới" + timer_already_running: "Để bắt đầu bộ hẹn giờ mới, trước tiên bạn phải dừng bộ hẹn giờ hiện tại:" + timer_already_stopped: "Không có bộ đếm thời gian hoạt động cho gói công việc này, bạn đã dừng nó ở cửa sổ khác chưa?" + tracking_time: "thời gian theo dõi" + button_stop: "Dừng bộ đếm thời gian hiện tại" two_factor_authentication: label_two_factor_authentication: "Xác thực hai yếu tố" watchers: - label_loading: lấy danh sách người theo dõi... + label_loading: đang tải người theo dõi... label_error_loading: Lỗi khi tải về danh sách người theo dõi - label_search_watchers: Tìm kiếm người theo dõi + label_search_watchers: Người theo dõi tìm kiếm label_add: Thêm người theo dõi - label_discard: Loại bỏ lựa chọn - typeahead_placeholder: Tìm kiếm người có thể theo dõi + label_discard: Hủy lựa chọn + typeahead_placeholder: Tìm kiếm người theo dõi có thể relation_labels: - parent: "Cha" - child: "Child" - children: "Con" + parent: "cha mẹ" + child: "đứa trẻ" + children: "bọn trẻ" relates: "Liên quan đến" - duplicates: "Trùng lặp" - duplicated: "Bị trùng lặp bởi" - blocks: "Chặn" + duplicates: "trùng lặp" + duplicated: "Nhân bản bởi" + blocks: "khối" blocked: "Bị chặn bởi" - precedes: "Tiền nhiệm" - follows: "Kế nhiệm" - includes: "Bao gồm" - partof: "Là một phần của" + precedes: "đi trước" + follows: "theo sau" + includes: "bao gồm" + partof: "Một phần của" requires: "Yêu cầu" - required: "Yêu cầu bởi" - relation_type: "Loại quan hệ" + required: "Được yêu cầu bởi" + relation_type: "kiểu quan hệ" relations_hierarchy: - parent_headline: "Cha" + parent_headline: "cha mẹ" hierarchy_headline: "Phân cấp" - children_headline: "Con" + children_headline: "bọn trẻ" relation_buttons: - set_parent: "Đặt cha" - change_parent: "Thay đổi cha" - remove_parent: "Xóa cha" - hierarchy_indent: "Thụt lề hệ thống phân cấp" - hierarchy_outdent: "Bỏ thụt lề hệ thống phân cấp" + set_parent: "Đặt cha mẹ" + change_parent: "Thay đổi cha mẹ" + remove_parent: "Xóa cha mẹ" + hierarchy_indent: "Thụt lề phân cấp" + hierarchy_outdent: "Phân cấp vượt mức" group_by_wp_type: "Nhóm theo loại gói công việc" group_by_relation_type: "Nhóm theo loại quan hệ" - add_parent: "Thêm cha hiện có" + add_parent: "Thêm cha mẹ hiện có" add_new_child: "Tạo con mới" create_new: "Tạo mới" add_existing: "Thêm hiện có" - add_existing_child: "Add child" + add_existing_child: "Thêm con" remove_child: "Xóa con" - add_new_relation: "Tạo quan hệ mới" - add_existing_relation: "Thêm quan hệ hiện có" - update_description: "Đặt hoặc cập nhật mô tả của quan hệ này" - toggle_description: "Chuyển đổi mô tả quan hệ" - update_relation: "Nhấp để thay đổi loại quan hệ" - show_relations: "Hiển thị các mối quan hệ" + add_new_relation: "Tạo mối quan hệ mới" + add_existing_relation: "Thêm mối quan hệ hiện có" + update_description: "Đặt hoặc cập nhật mô tả của mối quan hệ này" + toggle_description: "Chuyển đổi mô tả mối quan hệ" + update_relation: "Bấm để thay đổi kiểu quan hệ" + show_relations: "Hiển thị mối quan hệ" add_predecessor: "Thêm người tiền nhiệm" - add_successor: "Add successor" + add_successor: "Thêm người kế vị" remove: "Xóa mối quan hệ" - save: "Lưu quan hệ" - abort: "Hủy" + save: "Lưu mối quan hệ" + abort: "Hủy bỏ" relations_autocomplete: - placeholder: "Gõ để tìm kiếm" - parent_placeholder: "Chọn cha mới hoặc nhấn escape để hủy." + placeholder: "Nhập để tìm kiếm" + parent_placeholder: "Chọn cha mẹ mới hoặc nhấn thoát để hủy." autocompleter: - placeholder: "Gõ để tìm kiếm" + placeholder: "Nhập để tìm kiếm" notFoundText: "Không tìm thấy mục nào" - search: "Tìm kiếm" + search: "tìm kiếm" project: placeholder: "Chọn dự án" repositories: select_tag: "Chọn từ khóa" - select_branch: "Chọn nhánh" + select_branch: "Chọn chi nhánh" field_value_enter_prompt: "Nhập giá trị cho '%{field}'" - project_menu_details: "Chi tiết" + project_menu_details: "chi tiết" sort: - sorted_asc: "Sắp xếp tăng dần đã được áp dụng, " - sorted_dsc: "Sắp xếp giảm dần đã được áp dụng, " - sorted_no: "Chưa sắp xếp, " + sorted_asc: "Áp dụng sắp xếp tăng dần," + sorted_dsc: "Áp dụng sắp xếp giảm dần," + sorted_no: "Không áp dụng sắp xếp," sorting_disabled: "sắp xếp bị vô hiệu hóa" activate_asc: "kích hoạt để áp dụng sắp xếp tăng dần" activate_dsc: "kích hoạt để áp dụng sắp xếp giảm dần" activate_no: "kích hoạt để loại bỏ sắp xếp" - text_work_packages_destroy_confirmation: "Bạn có chắc chắn muốn xóa gói công việc(s) đã chọn không?" + text_work_packages_destroy_confirmation: "Bạn có chắc chắn muốn xóa (các) gói công việc đã chọn không?" text_query_destroy_confirmation: "Bạn muốn xóa các mục đã chọn?" tl_toolbar: zooms: "Mức thu phóng" - outlines: "Mức phân cấp" + outlines: "Cấp độ phân cấp" upsell: - ee_only: "Bổ sung phiên bản Doanh nghiệp" + ee_only: "Tiện ích bổ sung cho phiên bản doanh nghiệp" wiki_formatting: - strong: "In đậm" - italic: "In nghiêng" - underline: "Gạch chân" - deleted: "Đã xóa" - code: "Mã nhúng" + strong: "đậm" + italic: "Nghiêng" + underline: "gạch chân" + deleted: "đã xóa" + code: "Mã nội tuyến" heading1: "Tiêu đề 1" heading2: "Tiêu đề 2" heading3: "Tiêu đề 3" unordered_list: "Danh sách không có thứ tự" - ordered_list: "Danh sách có thứ tự" - quote: "Trích dẫn" + ordered_list: "Danh sách đặt hàng" + quote: "trích dẫn" unquote: "Bỏ trích dẫn" - preformatted_text: "Văn bản định dạng trước" - wiki_link: "Liên kết đến trang Wiki" - image: "Hình ảnh" + preformatted_text: "Văn bản được định dạng sẵn" + wiki_link: "Liên kết tới một trang Wiki" + image: "hình ảnh" sharing: - share: "Chia sẻ" + share: "chia sẻ" selected_count: "%{count} đã chọn" selection: - mixed: "Lẫn lộn" + mixed: "Hỗn hợp" work_packages: bulk_actions: edit: "Chỉnh sửa hàng loạt" delete: "Xoá hàng loạt" - duplicate: "Bulk duplicate" + duplicate: "Sao chép hàng loạt" move: "Thay đổi hàng loạt dự án" button_clear: "Xóa" comment_added: "Bình luận được thêm thành công." comment_send_failed: "Lỗi đã xảy ra. Không thể gửi bình luận." comment_updated: "Các bình luận đã được cập nhật thành công." - confirm_edit_cancel: "Bạn có chắc bạn muốn hủy bỏ chỉnh sửa work package?" - description_filter: "Bộ lọc" + confirm_edit_cancel: "Bạn có chắc chắn muốn hủy việc chỉnh sửa gói công việc không?" + description_filter: "bộ lọc" description_enter_text: "Nhập văn bản" description_options_hide: "Ẩn tùy chọn" description_options_show: "Hiển thị tùy chọn" edit_attribute: "%{attribute} - chỉnh sửa" key_value: "%{key}: %{value}" - label_enable_multi_select: "Kích hoạt chọn nhiều" - label_disable_multi_select: "Vô hiệu hóa chọn nhiều" + label_enable_multi_select: "Bật nhiều lựa chọn" + label_disable_multi_select: "Vô hiệu hóa nhiều lựa chọn" label_filter_add: "Thêm bộ lọc" label_filter_by_text: "Lọc bởi %text" - label_options: "Tuỳ chọn" - label_column_multiselect: "Trường thả xuống kết hợp: Chọn bằng các phím mũi tên, xác nhận lựa chọn bằng enter, xóa bằng phím backspace" - message_error_during_bulk_delete: Đã xảy ra lỗi khi cố gắng xóa gói công việc. - message_successful_bulk_delete: Đã xóa gói công việc thành công. - message_successful_show_in_fullscreen: "Click vào đây để mở work package này trong chế độ xem toàn màn hình." - message_view_spent_time: "Hiển thị thời gian đã sử dụng cho gói công việc này" - message_work_package_read_only: "Gói công việc bị khóa ở trạng thái này. Không thể thay đổi thuộc tính khác ngoài trạng thái." - message_work_package_status_blocked: "Trạng thái gói công việc không thể viết do trạng thái đã đóng và phiên bản đã đóng được gán." - placeholder_filter_by_text: "Chủ đề, mô tả, bình luận, ..." + label_options: "tùy chọn" + label_column_multiselect: "Trường thả xuống kết hợp: Chọn bằng phím mũi tên, xác nhận lựa chọn bằng enter, xóa bằng phím lùi" + message_error_during_bulk_delete: Đã xảy ra lỗi khi cố xóa gói công việc. + message_successful_bulk_delete: Đã xóa thành công các gói công việc. + message_successful_show_in_fullscreen: "Bấm vào đây để mở gói công việc này ở chế độ xem toàn màn hình." + message_view_spent_time: "Hiển thị thời gian dành cho gói công việc này" + message_work_package_read_only: "Gói công việc bị khóa ở trạng thái này. Không có thuộc tính nào khác ngoài trạng thái có thể được thay đổi." + message_work_package_status_blocked: "Trạng thái gói công việc không thể ghi được do trạng thái đóng và phiên bản đóng đang được chỉ định." + placeholder_filter_by_text: "Chủ đề, mô tả, nhận xét,..." progress: - title: "Ước lượng công việc và tiến độ" + title: "Ước tính và tiến độ công việc" baseline: addition_label: "Thêm vào chế độ xem trong khoảng thời gian so sánh" - removal_label: "Gỡ bỏ khỏi chế độ xem trong khoảng thời gian so sánh" - modification_label: "Đã thay đổi trong khoảng thời gian so sánh" - column_incompatible: "Cột này không hiển thị thay đổi trong chế độ Baseline." + removal_label: "Đã bị xóa khỏi chế độ xem trong khoảng thời gian so sánh" + modification_label: "Được sửa đổi trong khoảng thời gian so sánh" + column_incompatible: "Cột này không hiển thị các thay đổi trong chế độ Đường cơ sở." filters: title: "Lọc gói công việc" - baseline_incompatible: "Thuộc tính bộ lọc này không được tính đến trong chế độ Baseline." - baseline_warning: "Chế độ Baseline đã được bật nhưng một số bộ lọc đang hoạt động của bạn không được đưa vào so sánh." + baseline_incompatible: "Thuộc tính bộ lọc này không được xem xét trong chế độ Đường cơ sở." + baseline_warning: "Chế độ cơ sở đang bật nhưng một số bộ lọc đang hoạt động của bạn không được đưa vào so sánh." inline_create: - title: "Click vào đây để thêm một work package mới vào danh sách này" + title: "Bấm vào đây để thêm gói công việc mới vào danh sách này" create: title: "Work package mới" header: "Mới %{type}" @@ -866,72 +867,72 @@ vi: header_with_parent: "Mới %{type} (Con của %{parent_type} #%{id})" button: "Tạo mới" duplicate: - title: "Duplicate work package" + title: "Gói công việc trùng lặp" hierarchy: show: "Hiển thị chế độ phân cấp" hide: "Ẩn chế độ phân cấp" toggle_button: "Nhấp để chuyển đổi chế độ phân cấp." - leaf: "Gói công việc lá ở cấp %{level}." - children_collapsed: "Mức phân cấp %{level}, bị thu gọn. Nhấp để hiển thị các con đã lọc" - children_expanded: "Mức phân cấp %{level}, đã mở rộng. Nhấp để thu gọn các con đã lọc" + leaf: "Lá gói công việc ở cấp độ %{level}." + children_collapsed: "Cấp bậc phân cấp %{level}, đã sụp đổ. Nhấn vào đây để hiển thị những đứa trẻ được lọc" + children_expanded: "Cấp độ phân cấp %{level}, được mở rộng. Bấm để thu gọn các con đã lọc" faulty_query: - title: Không thể tải gói công việc. - description: Trình bày của bạn là sai và không thể được xử lý. + title: Không thể tải các gói công việc. + description: Chế độ xem của bạn bị sai và không thể xử lý được. no_results: - title: Không có gói công việc để hiển thị. - description: Không có gì được tạo ra hoặc tất cả các gói công việc đã được lọc. - limited_results: Chỉ có %{count} gói công việc có thể hiển thị trong chế độ sắp xếp thủ công. Vui lòng giảm số lượng kết quả bằng cách lọc, hoặc chuyển sang chế độ sắp xếp tự động. + title: Không có gói công việc nào để hiển thị. + description: Không có gói nào được tạo hoặc tất cả các gói công việc đều bị lọc ra. + limited_results: Chỉ các gói công việc %{count} mới có thể được hiển thị ở chế độ sắp xếp thủ công. Vui lòng giảm kết quả bằng cách lọc hoặc chuyển sang sắp xếp tự động. property_groups: - details: "Chi tiết" - people: "Người" - estimatesAndTime: "Ước tính thời gian &" - other: "Khác" + details: "chi tiết" + people: "mọi người" + estimatesAndTime: "Ước tính & Thời gian" + other: "khác" properties: - assignee: "Người được giao" - author: "Tác giả" - createdAt: "Tạo ngày" - description: "Mô tả" - date: "Ngày" + assignee: "Người được chuyển nhượng" + author: "tác giả" + createdAt: "Được tạo vào" + description: "mô tả" + date: "ngày" percentComplete: "% Hoàn thành" - percentCompleteAlternative: "Tiến độ" + percentCompleteAlternative: "tiến bộ" dueDate: "Ngày kết thúc" - duration: "Thời gian" - spentTime: "Thời gian" - category: "Thể loại" + duration: "thời lượng" + spentTime: "dành thời gian" + category: "thể loại" percentageDone: "Phần trăm hoàn thành" - priority: "Độ ưu tiên" - projectName: "Dự án" + priority: "ưu tiên" + projectName: "dự án" remainingWork: "Công việc còn lại" remainingWorkAlternative: "Số giờ còn lại" - responsible: "Chịu trách nhiệm" + responsible: "chịu trách nhiệm" startDate: "Ngày bắt đầu" - status: "Trạng thái" + status: "trạng thái" subject: "Chủ đề" - subproject: "Dự án con" - title: "Tiêu đề" - type: "Kiểu" - updatedAt: "Được cập nhật vào lúc" + subproject: "dự án con" + title: "tiêu đề" + type: "loại" + updatedAt: "Đã cập nhật vào" versionName: "Phiên bản" version: "Phiên bản" - work: "Công việc" + work: "làm việc" workAlternative: "Thời gian dự kiến" remainingTime: "Công việc còn lại" default_queries: manually_sorted: "Truy vấn được sắp xếp thủ công mới" latest_activity: "Hoạt động mới nhất" - created_by_me: "Do tôi tạo ra" - assigned_to_me: "Được phân công cho tôi" - recently_created: "Đã được tạo gần đây" - all_open: "Mở tất cả" - overdue: "Quá hạn" + created_by_me: "Được tạo bởi tôi" + assigned_to_me: "Đã giao cho tôi" + recently_created: "Được tạo gần đây" + all_open: "Tất cả đều mở" + overdue: "quá hạn" summary: "Tóm tắt" - shared_with_users: "Chia sẻ với người dùng" - shared_with_me: "Chia sẻ với tôi" + shared_with_users: "Được chia sẻ với người dùng" + shared_with_me: "Đã chia sẻ với tôi" jump_marks: - pagination: "Viet nam" - label_pagination: "Viet Nam" - content: "Nhảy đến nội dung" - label_content: "Nhấp vào đây để bỏ qua menu và đến nội dung" + pagination: "Chuyển đến phân trang bảng" + label_pagination: "Bấm vào đây để bỏ qua bảng gói công việc và chuyển sang phân trang" + content: "Chuyển đến nội dung" + label_content: "Nhấn vào đây để bỏ qua menu và đi tới nội dung" placeholders: default: "-" date: "Chọn ngày" @@ -939,8 +940,8 @@ vi: column_names: "Cột" group_by: "Nhóm kết quả bởi" group: "Nhóm theo" - group_by_disabled_by_hierarchy: "Nhóm theo bị vô hiệu hóa do chế độ phân cấp đang hoạt động." - hierarchy_disabled_by_group_by: "Chế độ phân cấp bị vô hiệu hóa do kết quả được nhóm theo %{column}." + group_by_disabled_by_hierarchy: "Nhóm theo bị tắt do chế độ phân cấp đang hoạt động." + hierarchy_disabled_by_group_by: "Chế độ phân cấp bị tắt do kết quả được nhóm theo %{column}." sort_ascending: "Sắp xếp tăng dần" sort_descending: "Sắp xếp giảm dần" move_column_left: "Di chuyển cột sang trái" @@ -949,162 +950,162 @@ vi: insert_columns: "Chèn cột" filters: "Bộ lọc" display_sums: "Hiển thị tổng" - confirm_edit_cancel: "Bạn có chắc bạn muốn hủy bỏ chỉnh sửa tên của trình bày này? Tiêu đề sẽ được đặt trở về giá trị trước đó." + confirm_edit_cancel: "Bạn có chắc chắn muốn hủy chỉnh sửa tên của chế độ xem này không? Tiêu đề sẽ được đặt trở lại giá trị trước đó." click_to_edit_query_name: "Nhấp vào chỉnh sửa tiêu đề của trình bày này." rename_query_placeholder: "Tên của trình bày" star_text: "Đánh dấu chế độ xem này là yêu thích và thêm vào thanh bên chế độ xem đã lưu ở bên trái." public_text: > - Công khai chế độ xem này, cho phép người dùng khác truy cập chế độ xem của bạn. Người dùng có quyền 'Quản lý chế độ xem công khai' có thể chỉnh sửa hoặc xóa truy vấn công khai. Điều này không ảnh hưởng đến khả năng nhìn thấy kết quả gói công việc trong chế độ xem đó và tùy thuộc vào quyền của họ, người dùng có thể thấy các kết quả khác nhau. + Xuất bản chế độ xem này, cho phép người dùng khác truy cập vào chế độ xem của bạn. Người dùng có quyền 'Quản lý chế độ xem công khai' có thể sửa đổi hoặc xóa truy vấn công khai. Điều này không ảnh hưởng đến khả năng hiển thị của kết quả gói công việc trong chế độ xem đó và tùy thuộc vào quyền của họ, người dùng có thể thấy các kết quả khác nhau. errors: unretrievable_query: "Không thể đọc trình bày từ URL" - not_found: "Không có trình bày nào đã được tạo ra" - duplicate_query_title: "Tên trình bày đã có, Bạn có muốn thay đổi không?" + not_found: "Không có quan điểm như vậy" + duplicate_query_title: "Tên của chế độ xem này đã tồn tại. Dù sao cũng thay đổi?" text_no_results: "Không chuyên mục nào được tìm thấy." reminders: - button_label: "Set reminder" + button_label: "Đặt lời nhắc" title: - new: "Set a reminder" - edit: "Edit reminder" - subtitle: "You will receive a notification for this work package at the chosen time." + new: "Đặt lời nhắc" + edit: "Chỉnh sửa lời nhắc" + subtitle: "Bạn sẽ nhận được thông báo về gói công việc này vào thời điểm đã chọn." presets: - tomorrow: "Tomorrow" - three_days: "In 3 days" - week: "In a week" - month: "In a month" - custom: "At a particular date/time" + tomorrow: "Ngày mai" + three_days: "trong 3 ngày" + week: "trong một tuần" + month: "trong một tháng" + custom: "Vào một ngày/giờ cụ thể" scheduling: - is_parent: "Ngày của gói công việc này được suy ra tự động từ các con của nó. Kích hoạt 'Lên lịch thủ công' để đặt ngày." - is_switched_from_manual_to_automatic: "Ngày của gói công việc này có thể cần phải được tính toán lại sau khi chuyển từ lên lịch thủ công sang tự động do mối quan hệ với các gói công việc khác." + is_parent: "Ngày của gói công việc này được tự động suy ra từ các phần tử con của nó. Kích hoạt 'Lập lịch thủ công' để đặt ngày." + is_switched_from_manual_to_automatic: "Ngày của gói công việc này có thể cần được tính toán lại sau khi chuyển từ lập lịch thủ công sang lập lịch tự động do mối quan hệ với các gói công việc khác." sharing: title: "Chia sẻ gói công việc" - show_all_users: "Hiển thị tất cả người dùng mà gói công việc đã được chia sẻ" + show_all_users: "Hiển thị tất cả người dùng đã được chia sẻ gói công việc" table: configure_button: "Cấu hình bảng gói công việc" - summary: "Bảng với hàng work package và cột của các thuộc tính work package." + summary: "Bảng có các hàng gói công việc và các cột thuộc tính gói công việc." text_inline_edit: "Hầu hết các ô của bảng này là các nút kích hoạt chức năng chỉnh sửa nội tuyến của thuộc tính đó." - text_sort_hint: "Với các liên kết trong các tiêu đề bảng bạn có thể sắp xếp, nhóm, sắp xếp lại, loại bỏ và thêm bảng cột." - text_select_hint: "Các hộp chọn nên được mở bằng 'ALT' và các phím mũi tên." + text_sort_hint: "Với các liên kết trong tiêu đề bảng, bạn có thể sắp xếp, nhóm, sắp xếp lại, xóa và thêm các cột trong bảng." + text_select_hint: "Các hộp chọn phải được mở bằng 'ALT' và phím mũi tên." table_configuration: - button: "Cấu hình bảng gói công việc này" - choose_display_mode: "Hiển thị gói công việc dưới dạng" + button: "Định cấu hình bảng gói công việc này" + choose_display_mode: "Hiển thị các gói công việc dưới dạng" modal_title: "Bảng cấu hình gói công việc" - embedded_tab_disabled: "Tab cấu hình này không khả dụng cho chế độ xem nhúng mà bạn đang chỉnh sửa." + embedded_tab_disabled: "Tab cấu hình này không có sẵn cho chế độ xem được nhúng mà bạn đang chỉnh sửa." default: "mặc định" display_settings: "Cài đặt hiển thị" default_mode: "Danh sách phẳng" hierarchy_mode: "Hệ thống phân cấp" - hierarchy_hint: "Tất cả các bảng được lọc sẽ được gắn với tiền bối của nó. Cây công việc có thể đươc mở ra hoặc thu lại." - display_sums_hint: "Hiển thị tổng của các cột số trong một hàng phía dưới bảng kết quả." - show_timeline_hint: "Hiển thị biểu đồ grantt ở bên phải của bảng. Bạn có thể thay đổi độ rộng của nó bằng cách kéo thanh chia giữa bảng và biểu đồ grantt." - highlighting: "Tô sáng" + hierarchy_hint: "Tất cả các kết quả bảng đã lọc sẽ được tăng cường với tổ tiên của chúng. Hệ thống phân cấp có thể được mở rộng và thu gọn." + display_sums_hint: "Hiển thị tổng của tất cả các thuộc tính có thể tính tổng trong một hàng bên dưới kết quả của bảng." + show_timeline_hint: "Hiển thị biểu đồ gantt tương tác ở phía bên phải của bảng. Bạn có thể thay đổi độ rộng của nó bằng cách kéo dải phân cách giữa bảng và biểu đồ gantt." + highlighting: "Làm nổi bật" highlighting_mode: - description: "Đánh dấu bằng màu sắc" - none: "Không tô sáng" - inline: "Thuộc tính(s) được tô sáng" + description: "Làm nổi bật bằng màu sắc" + none: "Không làm nổi bật" + inline: "(Các) thuộc tính được đánh dấu" inline_all: "Tất cả thuộc tính" - entire_row_by: "Toàn bộ hàng theo" - status: "Trạng thái" - priority: "Độ ưu tiên" - type: "Kiểu" + entire_row_by: "Toàn bộ hàng bởi" + status: "trạng thái" + priority: "ưu tiên" + type: "loại" sorting_mode: - description: "Chọn chế độ sắp xếp gói công việc của bạn:" - automatic: "Tự động" - manually: "Thủ công" - warning: "Bạn sẽ mất sắp xếp trước đó khi kích hoạt chế độ sắp xếp tự động." - columns_help_text: "Sử dụng trường đầu vào phía trên để thêm cột vào chế độ xem bảng của bạn. Bạn có thể kéo và thả các cột để thay đổi thứ tự của chúng." + description: "Chọn chế độ sắp xếp các gói Công việc của bạn:" + automatic: "tự động" + manually: "thủ công" + warning: "Bạn sẽ mất cách sắp xếp trước đó khi kích hoạt chế độ sắp xếp tự động." + columns_help_text: "Sử dụng trường nhập ở trên để thêm cột vào chế độ xem bảng của bạn. Bạn có thể kéo và thả các cột để sắp xếp lại chúng." relation_filters: filter_work_packages_by_relation_type: "Lọc các gói công việc theo loại quan hệ" tabs: - overview: Tổng quan - activity: Hoạt động - relations: Các mối quan hệ - watchers: Người theo dõi - files: Tập tin + overview: tổng quan + activity: hoạt động + relations: quan hệ + watchers: người theo dõi + files: tập tin time_relative: days: "ngày" weeks: "tuần" - months: "tháng" + months: "Tháng" toolbar: settings: - configure_view: "Cấu hình chế độ xem" + configure_view: "Định cấu hình chế độ xem" columns: "Cột" sort_by: "Sắp xếp theo" group_by: "Nhóm theo" display_sums: "Hiển thị tổng" - display_hierarchy: "Hiển thị phân cấp" - hide_hierarchy: "Ẩn phân cấp" - hide_sums: "Ẩn tổng" - save: "Lưu" + display_hierarchy: "Hiển thị thứ bậc" + hide_hierarchy: "Ẩn thứ bậc" + hide_sums: "Ẩn số tiền" + save: "lưu lại" save_as: "Lưu dưới dạng" - export: "Xuất" + export: "xuất khẩu" visibility_settings: "Cài đặt hiển thị" share_calendar: "Đăng ký lịch" page_settings: "Đổi tên trang" - delete: "Xoá" - filter: "Bộ lọc" + delete: "xóa" + filter: "bộ lọc" unselected_title: "Work Package" search_query_label: "Tìm trang đã lưu" modals: - label_name: "Tên" + label_name: "tên" label_delete_page: "Xóa trang hiện tại" button_apply: "Áp dụng" - button_save: "Lưu" + button_save: "lưu lại" button_submit: "Gửi" - button_cancel: "Hủy" - button_delete: "Xoá" + button_cancel: "Hủy bỏ" + button_delete: "xóa" form_submit: title: "Xác nhận để tiếp tục" text: "Bạn có thực sự muốn thực hiện thao tác này?" destroy_work_package: title: "Xác nhận xóa %{label}" single_text: "Bạn có chắc chắn muốn xóa gói công việc" - bulk_text: "Bạn có chắc chắn muốn xóa các %{label} sau đây?" + bulk_text: "Bạn có chắc chắn muốn xóa %{label} sau đây không?" has_children: "Gói công việc có %{childUnits}:" - confirm_deletion_children: "Tôi công nhận rằng TẤT CẢ các con của các gói công việc được liệt kê sẽ bị xóa đệ quy." - deletes_children: "Các gói công việc con cũng sẽ bị xóa theo." + confirm_deletion_children: "Tôi xác nhận rằng TẤT CẢ các gói công việc kế thừa được liệt kê sẽ bị xóa đệ quy." + deletes_children: "Tất cả các gói công việc con và con cháu của chúng cũng sẽ bị xóa đệ quy." destroy_time_entry: - title: "Xác nhận xóa mục thời gian" - text: "Bạn có chắc chắn muốn xóa mục thời gian sau đây không?" - notice_no_results_to_display: "Không có kết quả để hiển thị." - notice_successful_create: "Tạo thành công." + title: "Xác nhận xóa mục nhập thời gian" + text: "Bạn có chắc chắn muốn xóa mục nhập thời gian sau đây không?" + notice_no_results_to_display: "Không có kết quả có thể nhìn thấy để hiển thị." + notice_successful_create: "Sáng tạo thành công." notice_successful_delete: "Xóa thành công." notice_successful_update: "Cập nhật thành công." - notice_job_started: "Công việc đã bắt đầu." - no_job_id: "No job ID returned from server." - invalid_job_response: "Invalid response from server." - notice_bad_request: "Yêu cầu không hợp lệ." + notice_job_started: "công việc bắt đầu." + no_job_id: "Không có ID công việc nào được trả về từ máy chủ." + invalid_job_response: "Phản hồi không hợp lệ từ máy chủ." + notice_bad_request: "Yêu cầu xấu." relations: - empty: Không tồn tại mối quan hệ + empty: Không có mối quan hệ nào tồn tại remove: Loại bỏ các mối quan hệ inplace: button_edit: "%{attribute}: Sửa" button_save: "%{attribute}: Lưu" button_cancel: "%{attribute}: Hủy" - button_save_all: "Lưu" - button_cancel_all: "Hủy" - link_formatting_help: "Định dạng văn bản trợ giúp" + button_save_all: "lưu lại" + button_cancel_all: "Hủy bỏ" + link_formatting_help: "Trợ giúp định dạng văn bản" btn_preview_enable: "Xem trước" btn_preview_disable: "Tắt xem trước" null_value_label: "Không có giá trị" clear_value_label: "-" errors: - required: "%{field} không thể để trống" + required: "%{field} không được để trống" number: "%{field} không phải là số hợp lệ" - maxlength: "%{field} không thể chứa hơn %{maxLength} digit(s)" - minlength: "%{field} không thể chứa ít hơn %{minLength} digit(s)" + maxlength: "%{field} không thể chứa nhiều hơn %{maxLength} chữ số" + minlength: "%{field} không thể chứa ít hơn %{minLength} chữ số" messages_on_field: "Mục này không hợp lệ: %{messages}" - error_could_not_resolve_version_name: "Không thể phân giải tên phiên bản" + error_could_not_resolve_version_name: "Không thể giải quyết tên phiên bản" error_could_not_resolve_user_name: "Không thể phân giải tên người dùng" - error_attachment_upload: "Tập tin tải lên thất bại.%{error}" + error_attachment_upload: "Không thể tải lên tệp: %{error}" error_attachment_upload_permission: "Bạn không có quyền tải lên tập tin vào tài nguyên này." units: workPackage: - other: "công việc" + other: "gói công việc" child_work_packages: - other: "%{count} tác phẩm gói phần mềm trẻ em" - hour_string: "%{hours} h" + other: "%{count} gói công việc trẻ em" + hour_string: "%{hours} giờ" hour: - one: "1 h" + one: "1 giờ" other: "%{count} giờ" zero: "0 giờ" day: @@ -1119,97 +1120,97 @@ vi: global_search: all_projects: "Cho tất cả các dự án" close_search: "Đóng tìm kiếm" - items_available: "%{count} items available" - direct_hit_available: "Work package with exact ID found. Press Enter to open it." - current_project_and_all_descendants: "Trong dự án này + các dự án con" + items_available: "%{count} mặt hàng có sẵn" + direct_hit_available: "Gói công việc có ID chính xác được tìm thấy. Nhấn Enter để mở nó." + current_project_and_all_descendants: "Trong dự án này + các tiểu dự án" current_project: "Trong dự án này" recently_viewed: "Đã xem gần đây" - search_placeholder_expanded: "Search work packages by subject, project, type, status or ID" + search_placeholder_expanded: "Tìm kiếm các gói công việc theo chủ đề, dự án, loại, trạng thái hoặc ID" title: all_projects: "mọi dự án" - project_and_subprojects: "và tất cả các dự án con" + project_and_subprojects: "và tất cả các tiểu dự án" search_for: "Tìm kiếm" views: - card: "Thẻ" - list: "Bảng" - timeline: "Biểu đồ sự kiện" + card: "thẻ" + list: "bàn" + timeline: "Gantt" invite_user_modal: invite: "Mời" - placeholder_add_tag: "Create placeholder user" + placeholder_add_tag: "Tạo người dùng giữ chỗ" exclusion_info: modal: - title: "Trạng thái loại trừ khỏi tổng hợp phân cấp" + title: "Trạng thái bị loại trừ khỏi tổng số thứ bậc" content: >- - Trạng thái '%{status_name}' đã được cấu hình để loại trừ khỏi tổng hợp của Công việc, Công việc còn lại và % Hoàn thành. Các tổng hợp không tính đến giá trị này. + Trạng thái '%{status_name}' đã được định cấu hình để loại trừ khỏi tổng số Công việc, Công việc còn lại và % Đã hoàn thành theo thứ bậc. Tổng số không tính đến giá trị này. favorite_projects: - no_results: "Bạn không có dự án yêu thích" - no_results_subtext: "Thêm một hoặc nhiều dự án yêu thích thông qua cái nhìn tổng quan hoặc trong danh sách dự án." + no_results: "Bạn không có dự án yêu thích nào" + no_results_subtext: "Thêm một hoặc nhiều dự án yêu thích thông qua phần tổng quan hoặc trong danh sách dự án." include_projects: - toggle_title: "Bao gồm dự án" - title: "Các dự án" + toggle_title: "Bao gồm các dự án" + title: "dự án" clear_selection: "Xóa lựa chọn" apply: "Áp dụng" selected_filter: all: "Tất cả dự án" - selected: "Chỉ các dự án được chọn" - search_placeholder: "Search projects..." - search_placeholder_favorites: "Search favorites..." - include_subprojects: "Bao gồm tất cả các dự án con" + selected: "Chỉ được chọn" + search_placeholder: "Tìm kiếm dự án..." + search_placeholder_favorites: "Tìm kiếm yêu thích..." + include_subprojects: "Bao gồm tất cả các tiểu dự án" tooltip: - include_all_selected: "Dự án đã được bao gồm vì đã bật Bao gồm tất cả các dự án con." - current_project: "Đây là dự án hiện tại của bạn." - does_not_match_search: "Dự án không khớp với tiêu chí tìm kiếm." - no_results: "Không có dự án nào khớp với tiêu chí tìm kiếm của bạn." - no_favorite_results: "No favorite project matches your search criteria." + include_all_selected: "Dự án đã được đưa vào kể từ khi tính năng Bao gồm tất cả các dự án con được bật." + current_project: "Đây là dự án hiện tại bạn đang tham gia." + does_not_match_search: "Dự án không phù hợp với tiêu chí tìm kiếm." + no_results: "Không có dự án nào phù hợp với tiêu chí tìm kiếm của bạn." + no_favorite_results: "Không có dự án yêu thích nào phù hợp với tiêu chí tìm kiếm của bạn." include_workspaces: - search_placeholder: "Search..." + search_placeholder: "Tìm kiếm..." types: - program: "Program" - portfolio: "Portfolio" + program: "chương trình" + portfolio: "danh mục đầu tư" baseline: - toggle_title: "Cơ sở" + toggle_title: "Đường cơ sở" clear: "Xóa" apply: "Áp dụng" - header_description: "Nổi bật các thay đổi đã thực hiện đối với danh sách này kể từ bất kỳ thời điểm nào trong quá khứ." + header_description: "Đánh dấu những thay đổi được thực hiện trong danh sách này kể từ bất kỳ thời điểm nào trong quá khứ." show_changes_since: "Hiển thị các thay đổi kể từ" - help_description: "Múi giờ tham chiếu cho cơ sở." + help_description: "Múi giờ tham chiếu cho đường cơ sở." time_description: "Theo giờ địa phương của bạn: %{datetime}" - time: "Thời gian" - from: "Từ" - to: "Đến" + time: "thời gian" + from: "từ" + to: "đến" drop_down: none: "-" yesterday: "hôm qua" - last_working_day: "ngày làm việc gần nhất" + last_working_day: "ngày làm việc cuối cùng" last_week: "tuần trước" last_month: "tháng trước" a_specific_date: "một ngày cụ thể" between_two_specific_dates: "giữa hai ngày cụ thể" legends: - changes_since: "Thay đổi kể từ" + changes_since: "Những thay đổi kể từ" changes_between: "Thay đổi giữa" - now_meets_filter_criteria: "Hiện tại đáp ứng tiêu chí lọc" + now_meets_filter_criteria: "Bây giờ đáp ứng tiêu chí lọc" no_longer_meets_filter_criteria: "Không còn đáp ứng tiêu chí lọc" - maintained_with_changes: "Duy trì với các thay đổi" - in_your_timezone: "Theo múi giờ của bạn:" + maintained_with_changes: "Duy trì với những thay đổi" + in_your_timezone: "Trong múi giờ địa phương của bạn:" icon_tooltip: added: "Đã thêm vào chế độ xem trong khoảng thời gian so sánh" - removed: "Đã xóa khỏi chế độ xem trong khoảng thời gian so sánh" - changed: "Duy trì với các thay đổi" + removed: "Đã bị xóa khỏi chế độ xem trong khoảng thời gian so sánh" + changed: "Được duy trì với các sửa đổi" forms: submit_success_message: "Form đã được gửi thành công" - load_error_message: "Đã có lỗi trong khi tải form" - validation_error_message: "Vui lòng sửa lỗi có trong mẫu" + load_error_message: "Đã xảy ra lỗi khi tải biểu mẫu" + validation_error_message: "Vui lòng sửa các lỗi có trong biểu mẫu" advanced_settings: "Cài đặt nâng cao" spot: filter_chip: remove: "Xóa" drop_modal: - focus_grab: "Đây là điểm tập trung cho các modal. Nhấn shift+tab để quay lại phần tử kích hoạt modal." - close: "Đóng modal" + focus_grab: "Đây là điểm neo tập trung cho các phương thức. Nhấn shift+tab để quay lại phần tử kích hoạt phương thức." + close: "Đóng phương thức" open_project_storage_modal: waiting_title: - timeout: "Hết thời gian" + timeout: "Hết giờ" waiting_subtitle: - network_off: "Có vấn đề về mạng." - network_on: "Mạng đã trở lại. Chúng tôi đang thử lại." + network_off: "Có một vấn đề về mạng." + network_on: "Mạng đã trở lại. Chúng tôi đang cố gắng." diff --git a/config/locales/crowdin/js-zh-CN.yml b/config/locales/crowdin/js-zh-CN.yml index 498b712670e..f00fe4c05ce 100644 --- a/config/locales/crowdin/js-zh-CN.yml +++ b/config/locales/crowdin/js-zh-CN.yml @@ -203,8 +203,8 @@ zh-CN: add_table: "添加相关工作包表" edit_query: "编辑查询" new_group: "新建组" - delete_group: "Delete group" - remove_attribute: "Remove from group" + delete_group: "删除群组" + remove_attribute: "从群组中移除" reset_to_defaults: "重置为默认值" working_days: calendar: @@ -426,7 +426,7 @@ zh-CN: label_remove_row: "移除行" label_report: "报告" label_repository_plural: "存储库" - label_resize_project_menu: "Resize project menu" + label_resize_project_menu: "调整项目菜单大小" label_save_as: "另存为" label_search_columns: "搜索列" label_select_watcher: "选择一个关注者..." diff --git a/config/locales/crowdin/ko.yml b/config/locales/crowdin/ko.yml index c36087069d8..a722ceb7bf5 100644 --- a/config/locales/crowdin/ko.yml +++ b/config/locales/crowdin/ko.yml @@ -111,21 +111,21 @@ ko: link: "webhook" mcp_configurations: index: - description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." - resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." - resources_submit: "Update resources" - tools_heading: "Tools" - tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." - tools_submit: "Update tools" + description: "모델 컨텍스트 프로토콜을 통해 AI 에이전트는 이 OpenProject 인스턴스에 의해 노출된 도구와 리소스를 사용자에게 제공할 수 있습니다." + resources_heading: "리소스" + resources_description: "OpenProject는 다음 도구를 구현합니다. 원하는 대로 각 도구를 활성화하고, 이름을 바꾸고, 설명할 수 있습니다. 자세한 내용은 [MCP 리소스의 문서](docs_url)를 참조하세요." + resources_submit: "리소스 업데이트" + tools_heading: "도구" + tools_description: "OpenProject는 다음 도구를 구현합니다. 원하는 대로 각 도구를 활성화하고, 이름을 바꾸고, 설명할 수 있습니다. 자세한 내용은 [MCP 도구의 문서](docs_url)를 참조하세요." + tools_submit: "도구 업데이트" multi_update: - success: "MCP configurations were updated successfully." + success: "MCP 구성이 업데이트되었습니다." server_form: - description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + description_caption: "MCP 서버에 연결하는 다른 애플리케이션에 대해 MCP 서버가 설명되는 방식입니다." + title_caption: "MCP 서버에 연결하는 애플리케이션에 표시되는 짧은 제목입니다." update: - failure: "MCP configuration could not be updated." - success: "MCP configuration was updated successfully." + failure: "MCP 구성을 업데이트할 수 없습니다." + success: "MCP 구성이 업데이트되었습니다." scim_clients: authentication_methods: sso: "ID 공급자의 JWT" @@ -723,11 +723,11 @@ ko: create_button: "만들기" name_label: "토큰 이름" created_dialog: - one_time_warning: "This is the only time you will see this token. Make sure to copy it now." + one_time_warning: "이번에만 이 토큰이 표시됩니다. 지금 이 토큰을 복사해야 합니다." token/api: title: "API 토큰이 생성되었습니다" token/rss: - title: "The RSS token has been generated" + title: "RSS 토큰이 생성되었습니다" failed_to_reset_token: "액세스 토큰을 재설정하지 못함: %{error}" failed_to_create_token: "액세스 토큰을 만들지 못했습니다: %{error}" failed_to_revoke_token: "액세스 토큰을 취소하지 못했습니다: %{error}" @@ -1245,9 +1245,9 @@ ko: port: "포트" tls_certificate_string: "LDAP 서버 SSL 인증서" mcp_configuration: - enabled: Enabled - title: Title - description: Description + enabled: 활성화됨 + title: 제목 + description: 설명 member: roles: "역할" notification: @@ -1506,7 +1506,7 @@ ko: even: "에 짝수를 입력해 주세요" exclusion: "예약됨" feature_disabled: '- 사용할 수 없습니다.' - feature_disabled_for_project: is disabled for this project. + feature_disabled_for_project: '- 이 프로젝트에서 비활성화되었습니다.' file_too_large: "은(는) 너무 큽니다. (최대 %{count} 바이트)" filter_does_not_exist: "필터가 존재하지 않습니다." format: "- 필요한 형식 '%{expected}'과(와) 일치하지 않습니다." @@ -1926,7 +1926,7 @@ ko: token/api: other: 액세스 토큰 token/rss: - other: "RSS tokens" + other: "RSS 토큰" type: other: "유형" user: "사용자" @@ -2406,7 +2406,7 @@ ko: baseline_comparison: 기준선 비교 board_view: 고급 보드 calculated_values: 계산된 값 - capture_external_links: Capture External Links + capture_external_links: 외부 링크 캡처 internal_comments: 내부 코멘트 custom_actions: 사용자 지정 작업 custom_field_hierarchies: 계층 @@ -2416,12 +2416,12 @@ ko: edit_attribute_groups: 특성 그룹 편집 gantt_pdf_export: Gantt PDF 내보내기 ldap_groups: LDAP 사용자 및 그룹 동기화 - mcp_server: MCP Server + mcp_server: MCP 서버 nextcloud_sso: Nextcloud 저장소용 Single Sign-On one_drive_sharepoint_file_storage: OneDrive/SharePoint 파일 저장소 placeholder_users: 플레이스홀더 사용자 portfolio_management: 포트폴리오 관리 - project_creation_wizard: Project initiation request + project_creation_wizard: 프로젝트 시작 요청 project_list_sharing: 프로젝트 목록 공유 readonly_work_packages: 읽기 전용 작업 패키지 scim_api: SCIM 서버 API @@ -2463,7 +2463,7 @@ ko: customize_life_cycle: description: "PM2 프로젝트 사이클 계획에서 제공되는 것과는 다른 프로젝트 단계를 만들고 구성합니다." capture_external_links: - description: "Prevent social engineering attacks by capturing and warning about external links before users visit them." + description: "사용자가 외부 링크를 방문하기 전에 캡처하고 경고를 표시하여 소셜 엔지니어링 공격을 방지하세요." work_package_query_relation_columns: description: "작업 패키지 목록에서 관계 또는 자식 요소를 확인해야 하나요?" edit_attribute_groups: @@ -2494,7 +2494,7 @@ ko: title: "사용자 지정 작업" description: "사용자 지정 작업은 상태, 역할, 유형 또는 프로젝트를 기반으로 특정 작업 패키지에서 사용할 수 있도록 미리 정의된 작업 세트에 대한 원클릭 바로 가기입니다." mcp_server: - description: "Integrate AI agents with your OpenProject instance through MCP." + description: "MCP를 통해 AI 에이전트를 OpenProject 인스턴스와 통합합니다." nextcloud_sso: title: "Nextcloud 저장소용 Single Sign-On" description: "Single Sign-On을 사용하여 Nextcloud 저장소에 대한 원활하고 안전한 인증을 활성화합니다. 액세스 관리를 간소화하고 사용자 편의성을 개선합니다." @@ -2507,7 +2507,7 @@ ko: virus_scanning: description: "OpenProject에서 업로드된 파일은 바이러스 검사를 수행한 후에 다른 사용자가 액세스해야 합니다." project_creation_wizard: - description: "Generate a step-by-step wizard to help project managers fill out a project initiation request." + description: "프로젝트 관리자가 프로젝트 시작 요청을 작성하는 데 도움이 되는 단계별 마법사를 생성합니다." placeholder_users: title: 플레이스홀더 사용자 description: > @@ -2806,15 +2806,15 @@ ko: #We need to include the version to invalidate outdated translations in other locales "17_1": new_features_title: > - The release contains various new features and improvements, such as: + 이 릴리스에는 다음과 같은 다양한 새로운 기능과 개선 사항이 포함되어 있습니다. new_features_list: - line_0: Automated project initiation (Enterprise add-on). - line_1: "Meetings: add new or existing work packages as outcomes." - line_2: "Meetings: show participant responses in iCal subscriptions." - line_3: "Recurring meetings: duplicate agenda items to the next occurrence." - line_4: "Release to Community: Attribute highlighting." - line_5: Warning before opening external links in user-provided content (Enterprise add-on). - line_6: Improved performance and user experience, including the Activity tab and Documents module. + line_0: 자동화된 프로젝트 시작(Enterprise 추가 기능). + line_1: "미팅: 새 작업 패키지 또는 기존 작업 패키지를 결과로 추가합니다." + line_2: "미팅: iCal 구독에서 참가자 응답을 표시합니다." + line_3: "반복 미팅: 의제 항목을 다음 항목에 복제합니다." + line_4: "커뮤니티에 릴리스: 특성이 강조 표시됩니다." + line_5: 사용자 제공 콘텐츠에서 외부 링크를 열기 전에 표시되는 경고입니다(Enterprise 추가 기능). + line_6: 활동 탭 및 문서 모듈을 비롯한 성능 및 사용자 환경이 개선되었습니다. links: upgrade_enterprise_edition: "Enterprise Edition으로 업그레이드" postgres_migration: "설치를 PostgreSQL로 마이그레이션" @@ -2882,17 +2882,17 @@ ko: instructions_after_error: "%{signin}을(를) 클릭하여 다시 로그인해 볼 수 있습니다. 오류가 계속되면 관리자에게 도움을 요청하세요." menus: admin: - ai: "Artificial Intelligence (AI)" + ai: "인공 지능(AI)" aggregation: "집계" api_and_webhooks: "API 및 webhook" mail_notification: "이메일 알림" mails_and_notifications: "이메일 및 알림" - mcp_configurations: "Model Context Protocol (MCP)" + mcp_configurations: "모델 컨텍스트 프로토콜(MCP)" quick_add: label: "추가…" my_account: access_tokens: - description: "Provider tokens are issued by OpenProject, allowing other applications to access it. Client tokens are issued by other applications, allowing OpenProject to access them." + description: "공급자 토큰은 OpenProject에서 발급하며, 다른 애플리케이션이 액세스하도록 허용합니다. 클라이언트 토큰은 다른 애플리케이션에서 발급하며, OpenProject가 액세스하도록 허용합니다." no_results: title: "표시할 수 있는 접근 토큰이 없습니다." description: "모두 비활성화 되었습니다. 관리 메뉴에서 가용 상태로 되돌릴 수 있습니다." @@ -2904,53 +2904,53 @@ ko: simple_revoke_confirmation: "이 토큰을 취소하시겠습니까?" tabs: client: - title: "Client tokens" + title: "클라이언트 토큰" provider: - title: "Provider tokens" + title: "공급자 토큰" token/api: - blank_description: "There is no API token yet. You can create one using the button below." - blank_title: "No API token" + blank_description: "아직 API 토큰이 없습니다. 아래 버튼을 사용하여 생성할 수 있습니다." + blank_title: "API 토큰 없음" title: "API" - table_title: "API tokens" + table_title: "API 토큰" text_hint: "API 토큰을 사용하면 타사 애플리케이션이 REST API를 통해 이 OpenProject 인스턴스와 통신할 수 있습니다." - static_token_name: "API token" + static_token_name: "API 토큰" disabled_text: "관리자가 API 토큰을 활성화하지 않았습니다. 이 기능을 사용하려면 관리자에게 문의하세요." - add_button: "API token" + add_button: "API 토큰" ical: - blank_description: "To add an iCalendar token, subscribe to a new or existing calendar from within the Calendar module of a project. You must have the necessary permissions." - blank_title: "No iCalendar token" + blank_description: "iCalendar 토큰을 추가하려면 프로젝트의 캘린더 모듈 내에서 새 캘린더 또는 기존 캘린더를 구독하세요. 필요한 권한이 있어야 합니다." + blank_title: "iCalendar 토큰 없음" title: "iCalendar" - table_title: "iCalendar tokens" + table_title: "iCalendar 토큰" text_hint_link: "iCalendar 토큰을 사용하여 사용자가 [OpenProject 캘린더를 구독](docs_url)하고 외부 클라이언트의 최신 작업 패키지 정보를 볼 수 있습니다." disabled_text: "관리자가 iCalendar 구독을 활성화하지 않았습니다. 이 기능을 사용하려면 관리자에게 문의하세요." oauth_application: - active_tokens: "Active tokens" - blank_description: "There is no third-party application access configured and active for you." - blank_title: "No OAuth application token" - last_used_at: "Last used at" + active_tokens: "활성 토큰" + blank_description: "타사 애플리케이션 액세스가 구성 및 활성화되어 있지 않습니다." + blank_title: "OAuth 애플리케이션 토큰 없음" + last_used_at: "마지막 사용" title: "OAuth" - table_title: "OAuth application tokens" - text_hint: "OAuth application tokens allow third-party applications to connect with this OpenProject instance." + table_title: "OAuth 애플리케이션 토큰" + text_hint: "OAuth 애플리케이션 토큰을 통해 타사 애플리케이션이 이 OpenProject 인스턴스와 연결할 수 있습니다." oauth_client: - blank_description: "There are no OAuth client tokens yet." - blank_title: "No OAuth client tokens" - failed: "An error occurred and the token couldn't be removed. Please try again later." - integration_type: "Integration type" - table_title: "OAuth client tokens" - text_hint: "OAuth client tokens allow this OpenProject instance to connect with external applications, such as file storages." + blank_description: "아직 OAuth 클라이언트 토큰이 없습니다." + blank_title: "OAuth 클라이언트 토큰 없음" + failed: "오류가 발생하여 토큰을 제거할 수 없습니다. 나중에 다시 시도하세요." + integration_type: "통합 유형" + table_title: "OAuth 클라이언트 토큰" + text_hint: "OAuth 클라이언트 토큰을 통해 이 OpenProject 인스턴스가 파일 저장소와 같은 외부 애플리케이션과 연결할 수 있습니다." title: "OAuth" - remove_token: "Do you really want to remove this token? You will need to login again on %{integration}." - removed: "OAuth client token successfully removed" - unknown_integration: "Unknown" + remove_token: "이 토큰을 제거하시겠습니까? %{integration}에서 다시 로그인해야 합니다." + removed: "OAuth 클라이언트 토큰이 제거되었습니다" + unknown_integration: "알 수 없음" token/rss: - add_button: "RSS token" - blank_description: "There is no RSS token yet. You can create one using the button below." - blank_title: "No RSS token" + add_button: "RSS 토큰" + blank_description: "아직 RSS 토큰이 없습니다. 아래 버튼을 사용하여 생성할 수 있습니다." + blank_title: "RSS 토큰 없음" title: "RSS" - table_title: "RSS tokens" - text_hint: "RSS tokens allow users to keep up with the latest changes in this OpenProject instance via an external RSS reader." - static_token_name: "RSS token" - disabled_text: "RSS tokens are not enabled by the administrator. Please contact your administrator to use this feature." + table_title: "RSS 토큰" + text_hint: "RSS 토큰을 사용하면 외부 RSS 리더를 통해 이 OpenProject 인스턴스의 최신 변경 사항을 확인할 수 있습니다." + static_token_name: "RSS 토큰" + disabled_text: "관리자가 RSS 토큰을 활성화하지 않았습니다. 이 기능을 사용하려면 관리자에게 문의하세요." storages: unknown_storage: "알 수 없는 저장소" notifications: @@ -3268,7 +3268,7 @@ ko: label_journal_diff: "설명 비교" label_language: "언어" label_languages: "언어" - label_external_links: "External links" + label_external_links: "외부 링크" label_locale: "언어 및 지역" label_jump_to_a_project: "프로젝트로 이동..." label_keyword_plural: "키워드" @@ -4257,9 +4257,9 @@ ko: setting_allowed_link_protocols: "허용된 링크 프로토콜" setting_allowed_link_protocols_text_html: >- 이러한 프로토콜이 작업 패키지 설명, 긴 텍스트 필드 및 코멘트에서 링크로 렌더링되도록 허용합니다. 예: %{tel_code} 또는 %{element_code}. 라인별로 하나의 프로토콜을 입력합니다.
    %{http_code}, %{https_code} 및 %{mailto_code} 프로토콜은 항상 허용됩니다. - setting_capture_external_links: "Capture external links" + setting_capture_external_links: "외부 링크 캡처" setting_capture_external_links_text: > - When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + 활성화된 경우, 서식이 지정된 텍스트의 모든 외부 링크는 애플리케이션을 종료하기 전에 경고 페이지를 통해 리디렉션됩니다. 따라서 잠재적인 악성 외부 웹사이트로부터 사용자를 보호할 수 있습니다. setting_after_first_login_redirect_url: "첫 번째 로그인 리디렉션" setting_after_first_login_redirect_url_text_html: > 첫 로그인 후 사용자를 리디렉션할 경로를 설정하세요. 비어 있으면 온보딩 투어의 홈페이지로 리디렉션됩니다.
    예: /my/page @@ -4273,16 +4273,16 @@ ko: CORS가 활성화된 경우, OpenProject API에 액세스하도록 허용된 원본이 있습니다.
    원본 헤더의 설명서에서 예상 값 지정 방법을 확인하세요. setting_apiv3_write_readonly_attributes: "읽기 전용 특성에 대한 쓰기 액세스" setting_apiv3_write_readonly_attributes_instructions: > - If enabled, the API will allow administrators to write static read-only attributes during creation, such as createdAt and author. + 활성화된 경우, API를 통해 관리자는 생성 중에 createdAt 및 author 등 정적 읽기 전용 특성을 작성할 수 있습니다. setting_apiv3_write_readonly_attributes_warning: > - This setting has a use-case for e.g., importing data, but allows administrators to impersonate the creation of items as other users. All creation requests are being logged however with the true author. + 이 설정에는 데이터 가져오기 등의 사용 사례가 있지만 관리자가 다른 사용자로 항목을 만든 것처럼 가장할 수 있습니다. 그러나 모든 생성 요청은 실제 작성자로 기록됩니다. setting_apiv3_write_readonly_attributes_additional: > - For more information on attributes and supported resources, please see the %{api_documentation_link}. + 특성 및 지원되는 리소스에 대한 자세한 내용은 %{api_documentation_link}에서 참조하세요. setting_apiv3_max_page_size: "최대 API 페이지 크기" setting_apiv3_max_page_size_instructions: > - Set the maximum page size the API will respond with. It will not be possible to perform API requests that return more values on a single page. + API가 응답할 최대 페이지 크기를 설정합니다. 단일 페이지에 더 많은 값을 반환하는 API 요청은 수행할 수 없습니다. setting_apiv3_max_page_size_warning: > - Please only change this value if you are sure why you need it. Setting to a high value will result in significant performance impacts, while a value lower than the per page options will cause errors in paginated views. + 필요한 이유가 확실한 경우에만 이 값을 변경하세요. 높은 값으로 설정하면 성능에 상당한 영향을 미치며, 페이지당 옵션보다 낮은 값은 페이지가 매겨진 보기에서 오류를 일으킵니다. setting_apiv3_docs: "설명서" setting_apiv3_docs_enabled: "문서 페이지 활성화" setting_apiv3_docs_enabled_instructions_html: > @@ -4467,7 +4467,7 @@ ko: omniauth_direct_login_hint_html: > 이 옵션이 활성화된 경우, 로그인 요청은 구성된 omniauth 공급자로 리디렉션됩니다. 로그인 드롭다운 및 로그인 페이지가 비활성화됩니다.
    참고: 또한 암호 로그인을 비활성화하지 않는 경우에는 이 옵션이 활성화된 상태에서 사용자가 %{internal_path} 로그인 페이지를 방문하여 내부에서 계속 로그인할 수 있습니다. remapping_existing_users_hint: > - If enabled, allows any configured identity provider to login existing users based on their username, even if the user never signed in through that provider before. This can be useful when migrating the OpenProject instance to a new SSO provider, but is not recommended when using a provider that is not trusted by all users of your instance. + 활성화된 경우, 사용자가 이전에 해당 공급자를 통해 로그인한 적이 없더라도, 구성된 모든 ID 공급자가 사용자 이름을 기반으로 기존 사용자에 로그인하도록 허용합니다. 이 옵션은 OpenProject 인스턴스를 새 SSO 공급자로 마이그레이션할 때 유용할 수 있지만 인스턴스의 일부 사용자가 신뢰하지 않는 공급자를 이용할 때는 권장되지 않습니다. attachments: whitelist_text_html: > 업로드된 파일의 유효한 파일 확장명 및/또는 MIME 형식 목록을 정의합니다.
    파일 확장명(예: %{ext_example}) 또는 MIME 형식(예: %{mime_example})을 입력합니다.
    모든 파일 형식을 업로드할 수 있도록 허용하려면 비워 둡니다. 여러 값이 허용됩니다(각 값에 대해 한 줄). @@ -4573,7 +4573,7 @@ ko: project_mandate: "프로젝트 위임" submission: description_template: > - **This work package was automatically created upon completion of the %{wizard_name} workflow.** A PDF artifact containing all submitted information has been generated and attached to this work package for reference and audit purposes. If you need to update or re-run the initiation steps, you can reopen the wizard at any time by using the link below: + **이 작업 패키지는 %{wizard_name} 워크플로를 완료하면 자동으로 생성됩니다.** 제출된 모든 정보가 포함된 PDF 아티팩트가 생성되어 참조 및 감사 목적으로 이 작업 패키지에 첨부되었습니다. 시작 단계를 업데이트하거나 다시 실행해야 하는 경우 아래 링크를 사용하여 언제든지 마법사를 다시 열 수 있습니다. description: "사용자가 프로젝트 시작 요청을 제출하면 요청 아티팩트가 PDF 파일로 첨부된 새 작업 패키지가 생성됩니다. 아래 설정은 이 새 작업 패키지의 유형, 상태 및 담당자를 정의합니다." work_package_type: "작업 패키지 유형" work_package_type_caption: "완료된 아티팩트를 저장하는 데 사용할 작업 패키지 유형입니다." @@ -4831,8 +4831,8 @@ ko: other: "일시적으로 잠김 (%{count} 로그인 시도 실패)" confirm_status_change: "%{name} 상태가 변경됩니다. 계속하시겠습니까?" deleted: "사용자 삭제됨" - error_status_change_self: "You cannot change your own user status." - error_admin_change_on_non_admin: "Only administrators can change the status of administrator users." + error_status_change_self: "자신의 사용자 상태는 변경할 수 없습니다." + error_admin_change_on_non_admin: "관리자만 관리자 사용자의 상태를 변경할 수 있습니다." error_status_change_failed: "다음 오류 때문에 사용자 상태 변경 실패함: %{errors}" invite: 이메일을 통해 사용자 초대 invited: 초대됨 @@ -5237,7 +5237,7 @@ ko: hashed_token: display_value_placeholder: "***" external_link_warning: - title: "Leaving OpenProject" - warning_message: "You are about to leave OpenProject and visit an external website. Please be aware that external websites are not under our control and may have different privacy and security policies." - continue_message: "Are you sure you want to proceed to the following external link?" - continue_button: "Continue to external website" + title: "OpenProject 종료" + warning_message: "OpenProject를 종료하고 외부 웹사이트를 방문하려고 합니다. 외부 웹사이트는 당사의 통제를 받지 않으며 개인정보 보호 및 보안 정책이 다를 수 있다는 점에 유의하시기 바랍니다." + continue_message: "다음 외부 링크로 계속하시겠습니까?" + continue_button: "외부 웹사이트로 계속하기" diff --git a/config/locales/crowdin/pl.yml b/config/locales/crowdin/pl.yml index a08da608752..574bdfd4926 100644 --- a/config/locales/crowdin/pl.yml +++ b/config/locales/crowdin/pl.yml @@ -111,21 +111,21 @@ pl: link: "webhook" mcp_configurations: index: - description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." - resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." - resources_submit: "Update resources" - tools_heading: "Tools" - tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." - tools_submit: "Update tools" + description: "Protokół kontekstu modelu umożliwia agentom AI dostarczanie użytkownikom narzędzi i zasobów udostępnianych przez to wystąpienie OpenProject." + resources_heading: "Zasoby" + resources_description: "OpenProject implementuje następujące narzędzia. Każde z nich może zostać włączone, przemianowane i opisane zgodnie z Twoim życzeniem. Aby uzyskać więcej informacji, zapoznaj się z [dokumentacją zasobów MCP](docs_url)." + resources_submit: "Zaktualizuj zasoby" + tools_heading: "Narzędzia" + tools_description: "OpenProject implementuje następujące narzędzia. Każde z nich może zostać włączone, przemianowane i opisane zgodnie z Twoim życzeniem. Aby uzyskać więcej informacji, zapoznaj się z [dokumentacją narzędzi MCP](docs_url)." + tools_submit: "Zaktualizuj narzędzia" multi_update: - success: "MCP configurations were updated successfully." + success: "Konfiguracje MCP zostały zaktualizowane." server_form: - description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + description_caption: "Sposób, w jaki serwer MCP będzie opisywany innym aplikacjom, które się z nim łączą." + title_caption: "Krótki tytuł wyświetlany aplikacjom łączącym się z serwerem MCP." update: - failure: "MCP configuration could not be updated." - success: "MCP configuration was updated successfully." + failure: "Nie można zaktualizować konfiguracji MCP." + success: "Konfiguracja MCP została zaktualizowana." scim_clients: authentication_methods: sso: "JWT od dostawcy tożsamości" @@ -741,11 +741,11 @@ pl: create_button: "Utwórz" name_label: "Nazwa tokena" created_dialog: - one_time_warning: "This is the only time you will see this token. Make sure to copy it now." + one_time_warning: "Jest to jedyny raz, gdy zobaczysz ten token. Koniecznie skopiuj go teraz." token/api: title: "Token interfejsu API został wygenerowany" token/rss: - title: "The RSS token has been generated" + title: "Token RSS został wygenerowany" failed_to_reset_token: "Nie można zresetować tokena dostępu: %{error}" failed_to_create_token: "Nie można utworzyć tokena dostępu: %{error}" failed_to_revoke_token: "Nie można unieważnić tokena dostępu: %{error}" @@ -756,8 +756,8 @@ pl: notice_rss_token_revoked: "Token RSS został usunięty. Aby utworzyć nowy token użyj linku w sekcji RSS." notice_ical_token_revoked: 'Token iCalendar „%{token_name}” kalendarza „%{calendar_name}” projektu „%{project_name}” został unieważniony. Adres URL iCalendar z tym tokenem jest teraz nieprawidłowy.' password_confirmation_dialog: - confirmation_required: "You need to enter your account password to confirm this change." - title: "Confirm your password to continue" + confirmation_required: "Aby potwierdzić tę zmianę, należy wprowadzić hasło do konta." + title: "Aby kontynuować, potwierdź hasło" news: index: no_results_title_text: Nie ma jeszcze żadnych wiadomości do wyświetlenia. @@ -1268,9 +1268,9 @@ pl: port: "Port" tls_certificate_string: "Certyfikat SSL serwera LDAP" mcp_configuration: - enabled: Enabled - title: Title - description: Description + enabled: Włączona + title: Tytuł + description: Opis member: roles: "Role" notification: @@ -1529,7 +1529,7 @@ pl: even: "musi być takie samo." exclusion: "jest już zajęte." feature_disabled: jest niedostępny. - feature_disabled_for_project: is disabled for this project. + feature_disabled_for_project: jest wyłączona dla tego projektu. file_too_large: "jest za długie (maksymalna wielkość to %{count} Bajtów)." filter_does_not_exist: "filtr nie istnieje." format: "nie pasuje do oczekiwanego formatu '%{expected}'." @@ -1988,15 +1988,15 @@ pl: many: Tokenów dostępu other: Tokenu dostępu token/rss: - one: "RSS token" - few: "RSS tokens" - many: "RSS tokens" - other: "RSS tokens" + one: "Token RSS" + few: "Tokeny RSS" + many: "Tokeny RSS" + other: "Tokeny RSS" type: - one: "Type" - few: "Types" - many: "Types" - other: "Types" + one: "Typ" + few: "Typy" + many: "Typy" + other: "Typy" user: "Użytkownik" version: "Wersja" workflow: "Przepływ pracy" @@ -2221,7 +2221,7 @@ pl: button_click_to_reveal: "Kliknij aby pokazać" button_close: "Zamknij" button_collapse_all: "Zwiń wszystko" - button_confirm: "Confirm" + button_confirm: "Potwierdź" button_configure: "Konfigurowanie" button_continue: "Dalej" button_complete: "Ukończ" @@ -2546,7 +2546,7 @@ pl: baseline_comparison: Porównania linii odniesienia board_view: Tablice zaawansowane calculated_values: Obliczane wartości - capture_external_links: Capture External Links + capture_external_links: Przechwyć linki zewnętrzne internal_comments: Komentarze wewnętrzne custom_actions: Działania niestandardowe custom_field_hierarchies: Hierarchie @@ -2556,12 +2556,12 @@ pl: edit_attribute_groups: Edytuj grupy atrybutów gantt_pdf_export: Eksport wykresu Gantta w formacie PDF ldap_groups: Synchronizacja użytkowników i grup LDAP - mcp_server: MCP Server + mcp_server: Serwer MCP nextcloud_sso: Logowanie jednokrotne do magazynu Nextcloud one_drive_sharepoint_file_storage: Magazyn plików OneDrive/SharePoint placeholder_users: Użytkownicy zastępczy portfolio_management: Zarządzanie portfoliami - project_creation_wizard: Project initiation request + project_creation_wizard: Żądanie zainicjowania projektu project_list_sharing: Udostępnianie listy projektów readonly_work_packages: Pakiety robocze tylko do odczytu scim_api: Interfejs API serwera SCIM @@ -2603,7 +2603,7 @@ pl: customize_life_cycle: description: "Twórz i organizuj fazy projektu inne niż zapewniane przez planowanie cyklu projektu PM2." capture_external_links: - description: "Prevent social engineering attacks by capturing and warning about external links before users visit them." + description: "Zapobiegaj atakom socjotechnicznym, przechwytując linki zewnętrzne i ostrzegając o nich, zanim użytkownicy je odwiedzą." work_package_query_relation_columns: description: "Chcesz zobaczyć relacje lub elementy podrzędne na liście pakietów roboczych?" edit_attribute_groups: @@ -2634,7 +2634,7 @@ pl: title: "Działania niestandardowe" description: "Akcje niestandardowe są skrótami do zestawu predefiniowanych akcji, które można udostępnić w określonych pakietach roboczych na podstawie statusu, roli, typu lub projektu." mcp_server: - description: "Integrate AI agents with your OpenProject instance through MCP." + description: "Zintegruj agentów AI ze swoim wystąpieniem OpenProject za pomocą usługi MCP." nextcloud_sso: title: "Logowanie jednokrotne do magazynu Nextcloud" description: "Włącz bezproblemowe, bezpieczne uwierzytelnianie dla swojego magazynu Nextcloud za pomocą logowania jednokrotnego. Uprość zarządzanie dostępem i zwiększ wygodę użytkowników." @@ -2647,7 +2647,7 @@ pl: virus_scanning: description: "Upewnij się, że przesłane do OpenProject pliki są przeskanowane pod kątem wirusów, zanim będą dostępne dla innych użytkowników." project_creation_wizard: - description: "Generate a step-by-step wizard to help project managers fill out a project initiation request." + description: "Wygeneruj kreatora krok po kroku, aby pomóc kierownikom projektów wypełnić żądanie zainicjowania projektu." placeholder_users: title: Użytkownicy zastępczy description: > @@ -2949,15 +2949,15 @@ pl: #We need to include the version to invalidate outdated translations in other locales "17_1": new_features_title: > - The release contains various new features and improvements, such as: + Wydanie zawiera różne nowe funkcje i ulepszenia, takie jak: new_features_list: - line_0: Automated project initiation (Enterprise add-on). - line_1: "Meetings: add new or existing work packages as outcomes." - line_2: "Meetings: show participant responses in iCal subscriptions." - line_3: "Recurring meetings: duplicate agenda items to the next occurrence." - line_4: "Release to Community: Attribute highlighting." - line_5: Warning before opening external links in user-provided content (Enterprise add-on). - line_6: Improved performance and user experience, including the Activity tab and Documents module. + line_0: Zautomatyzowane inicjowanie projektów (dodatek wersji Enterprise). + line_1: "Spotkania: dodawaj nowe lub istniejące pakiety robocze jako wyniki." + line_2: "Spotkania: pokaż odpowiedzi uczestników w subskrypcjach iCal." + line_3: "Spotkania cykliczne: duplikuj punkty planu spotkania w następnym wystąpieniu." + line_4: "Udostępnianie społeczności: wyróżnianie atrybutów." + line_5: Ostrzeżenie przed otwarciem linków zewnętrznych w treści dostarczonej przez użytkownika (dodatek wersji Enterprise). + line_6: Ulepszona wydajność i wrażenia użytkownika, w tym karta Aktywność i moduł Dokumenty. links: upgrade_enterprise_edition: "Aktualizuj do wersji Enterprise" postgres_migration: "Migrowanie instalacji do PostgreSQL" @@ -3025,7 +3025,7 @@ pl: instructions_after_error: "Można spróbować się zalogować ponownie klikając %{signin}. Jeśli ten błąd będzie się powtarzał, poproś swojego admina o pomoc." menus: admin: - ai: "Artificial Intelligence (AI)" + ai: "Sztuczna inteligencja (AI)" aggregation: "Agregacja" api_and_webhooks: "Interfejs API i webhooki" mail_notification: "Powiadomienia e-mail" @@ -3035,7 +3035,7 @@ pl: label: "Dodaj…" my_account: access_tokens: - description: "Provider tokens are issued by OpenProject, allowing other applications to access it. Client tokens are issued by other applications, allowing OpenProject to access them." + description: "Tokeny dostawcy są wydawane przez OpenProject, co umożliwia dostęp do nich innym aplikacjom. Tokeny klienta są wydawane przez inne aplikacje, co umożliwia dostęp do nich oprogramowaniu OpenProject." no_results: title: "Brak tokenów dostępu" description: "Wszystkie elementy zostały wyłączone. Można je ponownie włączyć w menu administratora." @@ -3047,53 +3047,53 @@ pl: simple_revoke_confirmation: "Czy na pewno chcesz unieważnić ten token?" tabs: client: - title: "Client tokens" + title: "Tokeny klienta" provider: - title: "Provider tokens" + title: "Tokeny dostawcy" token/api: - blank_description: "There is no API token yet. You can create one using the button below." - blank_title: "No API token" + blank_description: "Nie ma jeszcze tokenu API. Możesz go utworzyć za pomocą poniższego przycisku." + blank_title: "Brak tokenu API" title: "API" - table_title: "API tokens" + table_title: "Tokeny API" text_hint: "Tokeny API pozwalają aplikacjom innych firm na komunikowanie się z tym wystąpieniem OpenProject za pośrednictwem interfejsu API REST." - static_token_name: "API token" + static_token_name: "Token API" disabled_text: "Tokeny API nie zostały włączone przez administratora. Aby użyć tej funkcji, skontaktuj się z administratorem." - add_button: "API token" + add_button: "Token API" ical: - blank_description: "To add an iCalendar token, subscribe to a new or existing calendar from within the Calendar module of a project. You must have the necessary permissions." - blank_title: "No iCalendar token" + blank_description: "Aby dodać token iCalendar, subskrybuj nowy lub istniejący kalendarz z modułu Kalendarz projektu. Musisz mieć niezbędne uprawnienia." + blank_title: "Brak tokenu iCalendar" title: "iCalendar" - table_title: "iCalendar tokens" + table_title: "Tokeny iCalendar" text_hint_link: "Tokeny iCalendar pozwalają użytkownikom [subskrybować kalendarze OpenProject](docs_url) i przeglądać aktualne informacje o pakietach roboczych z klientów zewnętrznych." disabled_text: "Subskrypcje iCalendar nie zostały włączone przez administratora. Aby użyć tej funkcji, skontaktuj się z administratorem." oauth_application: - active_tokens: "Active tokens" - blank_description: "There is no third-party application access configured and active for you." - blank_title: "No OAuth application token" - last_used_at: "Last used at" + active_tokens: "Aktywne tokeny" + blank_description: "Nie masz skonfigurowanego i aktywnego dostępu do aplikacji innych firm." + blank_title: "Brak tokenu aplikacji OAuth" + last_used_at: "Ostatnie użycie" title: "OAuth" - table_title: "OAuth application tokens" - text_hint: "OAuth application tokens allow third-party applications to connect with this OpenProject instance." + table_title: "Tokeny aplikacji OAuth" + text_hint: "Tokeny aplikacji OAuth pozwalają aplikacjom innych firm na łączenie się z tym wystąpieniem OpenProject." oauth_client: - blank_description: "There are no OAuth client tokens yet." - blank_title: "No OAuth client tokens" - failed: "An error occurred and the token couldn't be removed. Please try again later." - integration_type: "Integration type" - table_title: "OAuth client tokens" - text_hint: "OAuth client tokens allow this OpenProject instance to connect with external applications, such as file storages." + blank_description: "Nie ma jeszcze tokenów klienta OAuth." + blank_title: "Brak tokenów klienta OAuth" + failed: "Wystąpił błąd i nie można było usunąć tokenu. Spróbuj ponownie później." + integration_type: "Typ integracji" + table_title: "Tokeny klienta OAuth" + text_hint: "Tokeny klienta OAuth umożliwiają temu wystąpieniu OpenProject łączenie się z aplikacjami zewnętrznymi, takimi jak magazyny plików." title: "OAuth" - remove_token: "Do you really want to remove this token? You will need to login again on %{integration}." - removed: "OAuth client token successfully removed" - unknown_integration: "Unknown" + remove_token: "Czy na pewno chcesz usunąć ten token? Trzeba będzie zalogować się ponownie w %{integration}." + removed: "Token klienta OAuth został usunięty" + unknown_integration: "Nieznany" token/rss: - add_button: "RSS token" - blank_description: "There is no RSS token yet. You can create one using the button below." - blank_title: "No RSS token" + add_button: "Token RSS" + blank_description: "Nie ma jeszcze tokenu RSS. Możesz go utworzyć za pomocą poniższego przycisku." + blank_title: "Brak tokenu RSS" title: "RSS" - table_title: "RSS tokens" - text_hint: "RSS tokens allow users to keep up with the latest changes in this OpenProject instance via an external RSS reader." - static_token_name: "RSS token" - disabled_text: "RSS tokens are not enabled by the administrator. Please contact your administrator to use this feature." + table_title: "Tokeny RSS" + text_hint: "Tokeny RSS pozwalają użytkownikom na otrzymywanie informacji o najnowszych zmianach w tym wystąpieniu OpenProject za pośrednictwem zewnętrznego czytnika RSS." + static_token_name: "Token RSS" + disabled_text: "Tokeny RSS nie zostały włączone przez administratora. Aby użyć tej funkcji, skontaktuj się z administratorem." storages: unknown_storage: "Nieznany magazyn" notifications: @@ -3411,7 +3411,7 @@ pl: label_journal_diff: "Opis porównania" label_language: "Język" label_languages: "Języki" - label_external_links: "External links" + label_external_links: "Linki zewnętrzne" label_locale: "Język i region" label_jump_to_a_project: "Skok do projektu..." label_keyword_plural: "Słowa kluczowe" @@ -4053,7 +4053,7 @@ pl: notice_not_authorized: "Brak uprawnień dostępu do tej strony." notice_not_authorized_archived_project: "The project you're trying to access has been archived." notice_requires_enterprise_token: "Brak tokenu wersji Enterprise lub nie zezwala ona na dostęp do tej strony." - notice_password_confirmation_failed: "The entered password is not correct." + notice_password_confirmation_failed: "Wprowadzone hasło jest nieprawidłowe." notice_principals_found_multiple: "Znaleziono %{number} wyników.\nNaciśnij Tab, aby wybrać pierwszy rezultat." notice_principals_found_single: "Znaleziono 1 wynik.\nNaciśnij Tab, aby go wybrać." notice_parent_item_not_found: "Nie znaleziono elementu nadrzędnego." @@ -4406,9 +4406,9 @@ pl: setting_allowed_link_protocols: "Dozwolone protokoły linków" setting_allowed_link_protocols_text_html: >- Zezwalaj na renderowanie tych protokołów jako linków w opisach pakietów roboczych, długich polach tekstowych i komentarzach. Na przykład %{tel_code} lub %{element_code}. Wprowadź każdy protokół w jednym wierszu.
    Protokoły %{http_code}, %{https_code} i %{mailto_code} są zawsze dozwolone. - setting_capture_external_links: "Capture external links" + setting_capture_external_links: "Przechwyć linki zewnętrzne" setting_capture_external_links_text: > - When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + Po włączeniu tej funkcji wszystkie linki zewnętrzne w tekście formatowanym będą przekierowywać na stronę z ostrzeżeniem przed opuszczeniem aplikacji. Pomaga to chronić użytkowników przed potencjalnie złośliwymi zewnętrznymi witrynami internetowymi. setting_after_first_login_redirect_url: "Przekierowanie pierwszego logowania" setting_after_first_login_redirect_url_text_html: > Ustaw ścieżkę przekierowania użytkowników po ich pierwszym zalogowaniu. Jeśli jest pusta, przekierowuje do strony głównej wycieczki wdrożeniowej.
    Przykład: /my/page @@ -4422,16 +4422,16 @@ pl: Jeśli mechanizm CORS jest włączony, są to źródła, których dostęp do interfejsu API OpenProject jest dozwolony.
    Sprawdź w dokumentacji nagłówka origin jak należy określić oczekiwane wartości. setting_apiv3_write_readonly_attributes: "Dostęp do zapisu do atrybutów tylko do odczytu" setting_apiv3_write_readonly_attributes_instructions: > - If enabled, the API will allow administrators to write static read-only attributes during creation, such as createdAt and author. + Jeśli funkcja ta jest włączona, interfejs API pozwoli administratorom na zapisywanie podczas tworzenia statycznych atrybutów tylko do odczytu, takich jak createdAt i author. setting_apiv3_write_readonly_attributes_warning: > - This setting has a use-case for e.g., importing data, but allows administrators to impersonate the creation of items as other users. All creation requests are being logged however with the true author. + To ustawienie ma zastosowanie np. do importowania danych, ale pozwala administratorom na podszywanie się pod innych użytkowników podczas tworzenia elementów. Wszystkie żądania utworzenia są jednak rejestrowane z prawdziwym autorem. setting_apiv3_write_readonly_attributes_additional: > - For more information on attributes and supported resources, please see the %{api_documentation_link}. + Więcej informacji na temat atrybutów i obsługiwanych zasobów można znaleźć na stronie %{api_documentation_link}. setting_apiv3_max_page_size: "Maksymalny rozmiar strony interfejsu API" setting_apiv3_max_page_size_instructions: > - Set the maximum page size the API will respond with. It will not be possible to perform API requests that return more values on a single page. + Ustaw maksymalny rozmiar strony, jakim będzie odpowiadać interfejs API. Nie będzie możliwe wykonywanie żądań interfejsu API, które zwracają więcej wartości na jednej stronie. setting_apiv3_max_page_size_warning: > - Please only change this value if you are sure why you need it. Setting to a high value will result in significant performance impacts, while a value lower than the per page options will cause errors in paginated views. + Zmieniaj tę wartość tylko mając pewność, że jest to potrzebne. Ustawienie wysokiej wartości znacznie wpływa na wydajność, a wartość niższa niż liczba opcji na jednej stronie spowoduje błędy w stronicowanych widokach. setting_apiv3_docs: "Dokumentacja" setting_apiv3_docs_enabled: "Włącz stronę z dokumentami" setting_apiv3_docs_enabled_instructions_html: > @@ -4616,7 +4616,7 @@ pl: omniauth_direct_login_hint_html: > Jeśli ta opcja jest aktywna, żądania logowania będą przekierowywane do skonfigurowanego dostawcy omniauth. Lista rozwijana logowania i strona logowania zostaną wyłączone.
    Uwaga: o ile nie wyłączono również logowania hasłem, przy włączonej tej opcji użytkownicy mogą nadal logować się wewnętrznie, odwiedzając stronę logowania %{internal_path}. remapping_existing_users_hint: > - If enabled, allows any configured identity provider to login existing users based on their username, even if the user never signed in through that provider before. This can be useful when migrating the OpenProject instance to a new SSO provider, but is not recommended when using a provider that is not trusted by all users of your instance. + Jeśli opcja ta jest włączona, umożliwia dowolnemu skonfigurowanemu dostawcy tożsamości logowanie istniejących użytkowników na podstawie ich nazw użytkowników, nawet jeśli użytkownik nigdy wcześniej nie logował się za pośrednictwem tego dostawcy. Może to być przydatne podczas migracji wystąpienia OpenProject do nowego dostawcy SSO, ale nie jest zalecane w przypadku korzystania z dostawcy, który nie jest zaufany dla wszystkich użytkowników wystąpienia. attachments: whitelist_text_html: > Zdefiniuj listę poprawnych rozszerzeń plików i/lub typów mime dla przesłanych plików.
    Wprowadź rozszerzenia plików (np. %{ext_example}) lub typy mime (np. %{mime_example}).
    Pozostaw puste, aby umożliwić przesłanie dowolnego typu pliku. Dozwolone wielokrotne wartości (jeden wiersz dla każdej wartości). @@ -4722,7 +4722,7 @@ pl: project_mandate: "Zlecenie projektowe" submission: description_template: > - **This work package was automatically created upon completion of the %{wizard_name} workflow.** A PDF artifact containing all submitted information has been generated and attached to this work package for reference and audit purposes. If you need to update or re-run the initiation steps, you can reopen the wizard at any time by using the link below: + **Ten pakiet roboczy został automatycznie utworzony po zakończeniu przepływu pracy %{wizard_name}. ** Artefakt PDF zawierający wszystkie przesłane informacje został wygenerowany i dołączony do tego pakietu roboczego w celach referencyjnych i związanych z audytem. Jeśli musisz zaktualizować lub ponownie uruchomić kroki inicjowania, możesz ponownie otworzyć kreatora w dowolnym momencie, korzystając z poniższego linku: description: "Gdy użytkownik prześle żądanie zainicjowania projektu, zostanie utworzony nowy pakiet roboczy z artefaktem żądania załączonym jako plik PDF. Poniższe ustawienia definiują typ, status i odbiorcę tego nowego pakietu roboczego." work_package_type: "Typ pakietu roboczego" work_package_type_caption: "Typ pakietu roboczego, który powinien być używany do przechowywania ukończonego artefaktu." @@ -4983,8 +4983,8 @@ pl: other: "tymczasowo zablokowane (%{count} nieudanych prób zalogowania)" confirm_status_change: "Zamierzasz zmienić status „%{name}”. Na pewno chcesz kontynuować?" deleted: "Usunięty uźytkownik" - error_status_change_self: "You cannot change your own user status." - error_admin_change_on_non_admin: "Only administrators can change the status of administrator users." + error_status_change_self: "Nie można zmienić własnego statusu użytkownika." + error_admin_change_on_non_admin: "Tylko administratorzy mogą zmieniać status użytkownika administratorów." error_status_change_failed: "Zmiana statusu użytkownika nie powiodło się z powodu następujących błędów: %{errors}" invite: Zaproś użytkownika przez email invited: zaproszony @@ -5392,7 +5392,7 @@ pl: hashed_token: display_value_placeholder: "***" external_link_warning: - title: "Leaving OpenProject" - warning_message: "You are about to leave OpenProject and visit an external website. Please be aware that external websites are not under our control and may have different privacy and security policies." - continue_message: "Are you sure you want to proceed to the following external link?" - continue_button: "Continue to external website" + title: "Opuszczanie OpenProject" + warning_message: "W ten sposób opuścisz OpenProject i odwiedzisz zewnętrzną witrynę internetową. Pamiętaj, że zewnętrzne witryny internetowe nie są pod naszą kontrolą i mogą mieć różne polityki prywatności i zabezpieczeń." + continue_message: "Czy na pewno chcesz przejść do następującego linku zewnętrznego?" + continue_button: "Przejdź do zewnętrznej witryny internetowej" diff --git a/config/locales/crowdin/uk.yml b/config/locales/crowdin/uk.yml index ff0279c7307..f6e1490f570 100644 --- a/config/locales/crowdin/uk.yml +++ b/config/locales/crowdin/uk.yml @@ -121,10 +121,10 @@ uk: multi_update: success: "Конфігурації MCP оновлено." server_form: - description_caption: "Як MCP-сервер буде описано іншим додаткам, які підключаються до нього." - title_caption: "Короткий заголовок, який показується додаткам, що підключаються до сервера MCP." + description_caption: "Опис сервера MCP для інших додатків, які підключаються до нього." + title_caption: "Короткий заголовок, який відображається для додатків, що підключаються до сервера MCP." update: - failure: "Конфігурацію MCP не можна оновити." + failure: "Не вдалось оновити конфігурацію MCP." success: "Конфігурацію MCP оновлено." scim_clients: authentication_methods: @@ -2544,7 +2544,7 @@ uk: baseline_comparison: Порівняння з вихідним рівнем board_view: Покращені Дошки calculated_values: Розраховані значення - capture_external_links: Захоплення зовнішніх посилань + capture_external_links: Відстеження зовнішніх посилань internal_comments: Внутрішні коментарі custom_actions: Користувацькі дії custom_field_hierarchies: Ієрархії @@ -2559,7 +2559,7 @@ uk: one_drive_sharepoint_file_storage: Сховище файлів OneDrive / SharePoint placeholder_users: Прототипи користувачів portfolio_management: Керування портфелем - project_creation_wizard: Запит на ініціювання проєкту + project_creation_wizard: Запит на запуск проєкту project_list_sharing: Спільне використання списку проєктів readonly_work_packages: Пакети робіт лише для читання scim_api: API сервера SCIM @@ -2601,7 +2601,7 @@ uk: customize_life_cycle: description: "Створюйте й упорядковуйте етапи й проєкту, відмінні від тих, що передбачено плануванням циклу проєкту PM2." capture_external_links: - description: "Запобігайте атакам соціальної інженерії, перехоплюючи зовнішні посилання й попереджаючи про них користувачів до того, як вони перейдуть за ними." + description: "Запобігайте атакам соціальної інженерії, виявляючи зовнішні посилання й попереджаючи про них користувачів перед переходом за ними." work_package_query_relation_columns: description: "Хочете переглядати зв’язки або дочірні елементи в списку пакетів робіт?" edit_attribute_groups: @@ -2645,7 +2645,7 @@ uk: virus_scanning: description: "Переконайтеся, що вивантажені в OpenProject файли перевіряються на віруси перед тим, як стають доступними для інших користувачів." project_creation_wizard: - description: "Створіть покроковий майстер, який допоможе менеджерам проєктів заповнити запит на ініціювання проєкту." + description: "Створіть покроковий майстер, який допоможе менеджерам проєктів заповнити запит на запуск проєкту." placeholder_users: title: Прототипи користувачів description: > @@ -2949,13 +2949,13 @@ uk: new_features_title: > Цей випуск містить різноманітні нові функції та покращення, такі як: new_features_list: - line_0: Автоматизована ініціація проектів (надбудова Enterprise). - line_1: "Наради: додайте нові або наявні робочі пакети як результати." + line_0: Автоматизований запуск проєктів (надбудова Enterprise). + line_1: "Наради: додавайте нові або наявні пакети робіт як підсумки." line_2: "Наради: показуйте відповіді учасників у підписках iCal." line_3: "Повторювані наради: копіюйте пункти порядку денного в наступну нараду." line_4: "Випуск Community: виділення атрибутів." line_5: Попередження перед відкриттям зовнішніх посилань у контенті, створеному користувачем (надбудова Enterprise). - line_6: Покращено продуктивність і взаємодію з користувачем, зокрема на вкладці «Діяльність» і в модулі «Документи». + line_6: Покращення продуктивності й взаємодії з користувачем, зокрема на вкладці «Дії» і в модулі «Документи». links: upgrade_enterprise_edition: "Оновлення до версії Enterprise" postgres_migration: "Міграція інсталяції в PostgreSQL" @@ -3028,12 +3028,12 @@ uk: api_and_webhooks: "API та вебгуки" mail_notification: "Email сповіщення" mails_and_notifications: "Електронні листи й сповіщення" - mcp_configurations: "Модель контекстного протоколу (MCP)" + mcp_configurations: "Model Context Protocol (MCP)" quick_add: label: "Додати…" my_account: access_tokens: - description: "Маркери постачальника послуг випускаються в OpenProject, що дає змогу іншим програмам отримувати до них доступ. Клієнтські маркери випускаються іншими програмами, що дає змогу OpenProject отримати до них доступ." + description: "Маркери постачальника послуг випускаються в OpenProject, що дає змогу іншим програмам отримувати до них доступ. Клієнтські маркери випускаються іншими додатками, що дає змогу OpenProject отримати до них доступ." no_results: title: "Немає маркерів доступу для відображення" description: "Всі вони були відключені. Їх можна повторно ввімкнути в меню адміністрування." @@ -3049,7 +3049,7 @@ uk: provider: title: "Маркери постачальника послуг" token/api: - blank_description: "У вас ще немає маркера API. Ви можете створити його за допомогою кнопки нижче." + blank_description: "У вас ще немає маркера API. Щоб створити його, натисніть кнопку нижче." blank_title: "Немає маркера API" title: "API" table_title: "Маркери API" diff --git a/config/locales/crowdin/vi.seeders.yml b/config/locales/crowdin/vi.seeders.yml index 569a21a8382..92d9b9b1015 100644 --- a/config/locales/crowdin/vi.seeders.yml +++ b/config/locales/crowdin/vi.seeders.yml @@ -7,131 +7,131 @@ vi: common: colors: item_0: - name: Xanh dương (tối) + name: Màu xanh (tối) item_1: - name: Xanh dương + name: Màu xanh item_2: - name: Xanh dương (sáng) + name: Màu xanh (sáng) item_3: - name: Xanh lá cây (sáng) + name: Xanh (sáng) item_4: - name: Xanh lá cây (tối) + name: Xanh (đậm) item_5: - name: Vàng + name: màu vàng item_6: - name: Cam + name: trái cam item_7: - name: Đỏ + name: màu đỏ item_8: - name: Đỏ tươi + name: Màu đỏ tươi item_9: - name: Trắng + name: trắng item_10: - name: Xám (sáng) + name: Màu xám (sáng) item_11: - name: Xám + name: Màu xám item_12: name: Màu xám (tối) item_13: - name: Đen + name: màu đen project_phase_colors: item_0: - name: PM2 Orange + name: PM2 Cam item_1: - name: PM2 Red + name: PM2 đỏ item_2: - name: PM2 Magenta + name: PM2 màu đỏ tươi item_3: - name: PM2 Green Yellow + name: PM2 Xanh Vàng project_query_roles: item_0: - name: Người xem truy vấn dự án + name: Trình xem truy vấn dự án item_1: - name: Người chỉnh sửa truy vấn dự án + name: Trình soạn thảo truy vấn dự án work_package_roles: item_0: - name: Người chỉnh sửa gói công việc + name: Trình chỉnh sửa gói công việc item_1: name: Người bình luận gói công việc item_2: - name: Người xem gói công việc + name: Trình xem gói công việc project_roles: item_0: name: Không là thành viên item_1: - name: Ẩn danh + name: vô danh item_2: - name: Thành viên + name: thành viên item_3: - name: Người đọc + name: người đọc item_4: - name: Quản trị dự án + name: Quản trị viên dự án global_roles: item_0: name: Nhân viên và quản lý dự án item_1: - name: Standard global role + name: Vai trò toàn cầu tiêu chuẩn standard: project_phases: item_0: - name: Initiating + name: Bắt đầu item_1: - name: Lập kế hoạch - start_gate: Ready for Planning + name: lập kế hoạch + start_gate: Sẵn sàng lập kế hoạch item_2: - name: Executing - start_gate: Ready for Executing + name: đang thực thi + start_gate: Sẵn sàng thực hiện item_3: - name: Closing - start_gate: Ready for Closing + name: Đóng cửa + start_gate: Sẵn sàng đóng cửa priorities: item_0: name: Thấp item_1: name: Bình Thường item_2: - name: Cao + name: cao item_3: - name: Ngay lập tức + name: ngay lập tức projects: demo-project: name: Dự án demo - status_explanation: Tất cả các nhiệm vụ đang theo đúng tiến độ. Những người liên quan biết nhiệm vụ của họ. Hệ thống đã được thiết lập hoàn chỉnh. - description: Đây là tóm tắt ngắn gọn về mục tiêu của dự án demo này. + status_explanation: Mọi công việc đều đúng tiến độ. Những người liên quan biết nhiệm vụ của họ. Hệ thống đã được thiết lập hoàn chỉnh. + description: Đây là bản tóm tắt ngắn gọn về mục tiêu của dự án demo này. news: item_0: - title: Chào mừng đến với dự án mẫu của bạn + title: Chào mừng đến với dự án demo của bạn summary: | Chúng tôi rất vui vì bạn đã tham gia. - Trong mô-đun này, bạn có thể truyền đạt tin tức dự án đến các thành viên trong nhóm của bạn. - description: Tin tức hiện tại + Trong mô-đun này, bạn có thể truyền đạt tin tức dự án cho các thành viên trong nhóm của mình. + description: Tin tức thực tế categories: - item_0: Danh mục 1 (cần thay đổi trong cài đặt Dự án) + item_0: Loại 1 (sẽ được thay đổi trong cài đặt Dự án) queries: item_0: - name: Kế hoạch dự án + name: kế hoạch dự án item_1: - name: Các mốc thời gian + name: Các cột mốc quan trọng item_2: name: Nhiệm vụ item_3: - name: Lịch trình nhóm + name: Người lập kế hoạch nhóm boards: kanban: - name: Bảng Kanban + name: bảng Kanban basic: name: Bảng cơ bản lists: item_0: - name: Danh sách mong muốn + name: danh sách mong muốn item_1: name: Danh sách ngắn item_2: - name: Danh sách ưu tiên cho hôm nay + name: Danh sách ưu tiên cho ngày hôm nay item_3: - name: Không bao giờ + name: không bao giờ parent_child: - name: Cấu trúc phân công công việc + name: Cấu trúc phân chia công việc project-overview: widgets: item_0: @@ -141,169 +141,169 @@ vi: options: name: Bắt đầu text: | - Chúng tôi rất vui vì bạn đã tham gia! Chúng tôi khuyên bạn nên thử một vài điều để bắt đầu với OpenProject. + Chúng tôi rất vui vì bạn đã tham gia! Chúng tôi khuyên bạn nên thử một số thứ để bắt đầu trong OpenProject. - Khám phá các tính năng quan trọng nhất với [Tour hướng dẫn]({{opSetting:base_url}}/projects/demo-project/work_packages/?start_onboarding_tour=true). + Khám phá những tính năng quan trọng nhất với [Guided Tour]({{opSetting:base_url}}/projects/demo-project/work_packages/?start_onboarding_tour=true của chúng tôi). _Hãy thử các bước sau:_ - 1. *Mời các thành viên mới vào dự án của bạn*: → Đi đến [Thành viên]({{opSetting:base_url}}/projects/demo-project/members) trong điều hướng dự án. - 2. *Xem công việc trong dự án của bạn*: → Đi đến [Nhiệm vụ]({{opSetting:base_url}}/projects/demo-project/work_packages) trong điều hướng dự án. - 3. *Tạo một gói công việc mới*: → Đi đến [Nhiệm vụ → Tạo]({{opSetting:base_url}}/projects/demo-project/work_packages/new). - 4. *Tạo và cập nhật kế hoạch dự án*: → Đi đến [Kế hoạch dự án]({{opSetting:base_url}}/projects/demo-project/work_packages?query_id=##query.id:demo_project__query__project_plan) trong điều hướng dự án. - 5. *Kích hoạt các mô-đun khác*: → Đi đến [Cài đặt dự án → Mô-đun]({{opSetting:base_url}}/projects/demo-project/settings/modules). - 6. *Hoàn thành các nhiệm vụ trong dự án*: → Đi đến [Nhiệm vụ → Nhiệm vụ]({{opSetting:base_url}}/projects/demo-project/work_packages/details/##wp.id:set_date_and_location_of_conference/overview?query_id=##query.id:demo_project__query__tasks). + 1. *Mời thành viên mới tham gia dự án của bạn*: → Đi tới [Members]({{opSetting:base_url}}/projects/demo-project/members) trong điều hướng dự án. + 2. *Xem công việc trong dự án của bạn*: → Đi tới [Work packages]({{opSetting:base_url}}/projects/demo-project/work_packages) trong điều hướng dự án. + 3. *Tạo gói công việc mới*: → Đi tới [Work packages → Create]({{opSetting:base_url}}/projects/demo-project/work_packages/new). + 4. *Tạo và cập nhật kế hoạch dự án*: → Truy cập [Project plan]({{opSetting:base_url}}/projects/demo-project/work_packages?query_id=##query.id:demo_project__query__project_plan) trong điều hướng dự án. + 5. *Kích hoạt thêm các mô-đun*: → Chuyển đến [Project settings → Modules]({{opSetting:base_url}}/projects/demo-project/settings/modules). + 6. *Hoàn thành nhiệm vụ của bạn trong dự án*: → Truy cập [Work packages → Tasks]({{opSetting:base_url}}/projects/demo-project/work_packages/details/##wp.id:set_date_and_location_of_conference/overview?query_id=##query.id:demo_project__query__tasks). - Tại đây bạn sẽ tìm thấy [Hướng dẫn người dùng](https://www.openproject.org/docs/user-guide/). - Hãy cho chúng tôi biết nếu bạn có bất kỳ câu hỏi nào hoặc cần hỗ trợ. Liên hệ với chúng tôi: [support[at]openproject.com](mailto:support@openproject.com). + Tại đây bạn sẽ tìm thấy [User Guides](https://www.openproject.org/docs/user-guide/) của chúng tôi. + Vui lòng cho chúng tôi biết nếu bạn có bất kỳ câu hỏi nào hoặc cần hỗ trợ. Liên hệ với chúng tôi: [support[at]openproject.com](mailto:support@openproject.com). item_5: options: - name: Work Packages + name: Gói công việc item_6: options: - name: Các mốc thời gian + name: Các cột mốc quan trọng work_packages: item_0: subject: Bắt đầu dự án item_1: - subject: Tổ chức hội nghị mã nguồn mở + subject: Tổ chức hội nghị nguồn mở children: item_0: - subject: Xác định ngày và địa điểm hội nghị + subject: Đặt ngày và địa điểm của hội nghị children: item_0: - subject: Gửi lời mời tới các diễn giả + subject: Gửi lời mời tới diễn giả item_1: - subject: Liên hệ với các đối tác tài trợ + subject: Liên hệ đối tác tài trợ item_2: - subject: Tạo tài liệu quảng cáo và phát tay + subject: Tạo tài liệu tài trợ và tài liệu phát tay item_1: - subject: Mời người tham gia hội nghị + subject: Mời người tham dự hội nghị item_2: subject: Thiết lập trang web hội nghị item_2: - subject: Hội nghị + subject: hội nghị item_3: - subject: Các nhiệm vụ theo dõi + subject: Nhiệm vụ tiếp theo children: item_0: subject: Tải bài thuyết trình lên trang web item_1: - subject: Tiệc cho những người ủng hộ hội nghị :-) + subject: Tiệc dành cho những người ủng hộ hội nghị :-) description: |- - * [ ] Bia - * [ ] Đồ ăn nhẹ - * [ ] Nhạc - * [ ] Thêm bia + * [ ] Bia + * [ ] Đồ ăn nhẹ + * [ ] Âm nhạc + * [ ] Thêm bia nữa item_4: subject: Kết thúc dự án wiki: | - _Trong wiki này, bạn có thể tạo và chỉnh sửa các trang và các trang con một cách hợp tác để tạo một wiki dự án._ + _Trong wiki này bạn có thể cộng tác tạo và chỉnh sửa các trang và trang con để tạo một dự án wiki._ **Bạn có thể:** - * Chèn văn bản và hình ảnh, cũng như sao chép và dán từ các tài liệu khác - * Tạo cấu trúc trang với các trang cha - * Bao gồm các trang wiki vào menu dự án - * Sử dụng macro để bao gồm, ví dụ, bảng nội dung, danh sách nhiệm vụ, hoặc biểu đồ Gantt - * Bao gồm các trang wiki trong các trường văn bản khác, ví dụ, trang tổng quan dự án - * Bao gồm liên kết đến các tài liệu khác + * Chèn văn bản và hình ảnh, đồng thời sao chép và dán từ các tài liệu khác + * Tạo hệ thống phân cấp trang với các trang mẹ + * Đưa các trang wiki vào menu dự án + * Sử dụng macro để bao gồm, ví dụ: mục lục, danh sách gói công việc hoặc biểu đồ Gantt + * Bao gồm các trang wiki trong các trường văn bản khác, ví dụ: trang tổng quan dự án + * Bao gồm các liên kết đến các tài liệu khác * Xem lịch sử thay đổi * Xem dưới dạng Markdown - Thêm thông tin: [https://www.openproject.org/docs/user-guide/wiki/](https://www.openproject.org/docs/user-guide/wiki/) + Thông tin thêm: [https://www.openproject.org/docs/user-guide/wiki/](https://www.openproject.org/docs/user-guide/wiki/) scrum-project: name: Dự án Scrum - status_explanation: Tất cả các nhiệm vụ đang theo đúng tiến độ. Những người liên quan biết nhiệm vụ của họ. Hệ thống đã được thiết lập hoàn chỉnh. - description: Đây là tóm tắt ngắn gọn về mục tiêu của dự án Scrum demo này. + status_explanation: Mọi công việc đều đúng tiến độ. Những người liên quan biết nhiệm vụ của họ. Hệ thống đã được thiết lập hoàn chỉnh. + description: Đây là bản tóm tắt ngắn gọn về mục tiêu của dự án Scrum demo này. news: item_0: - title: Chào mừng bạn đến với dự án Scrum demo của bạn + title: Chào mừng bạn đến với dự án demo Scrum của bạn summary: | Chúng tôi rất vui vì bạn đã tham gia. - Trong mô-đun này, bạn có thể truyền đạt tin tức dự án đến các thành viên trong nhóm của bạn. + Trong mô-đun này, bạn có thể truyền đạt tin tức dự án cho các thành viên trong nhóm của mình. versions: item_0: - name: Danh sách lỗi + name: Tồn đọng lỗi item_1: - name: Danh sách sản phẩm + name: Tồn đọng sản phẩm item_2: - name: Sprint 1 + name: Chạy nước rút 1 wiki: - title: Sprint 1 + title: Chạy nước rút 1 content: | - ### Cuộc họp lập kế hoạch Sprint + ### Họp lập kế hoạch Sprint - _Vui lòng ghi lại các chủ đề của cuộc họp lập kế hoạch Sprint ở đây_ + _Vui lòng ghi lại các chủ đề ở đây cho cuộc họp lập kế hoạch Sprint_ - * Giới hạn thời gian (8 h) - * Đầu vào: Danh sách sản phẩm - * Đầu ra: Danh sách Sprint + * Thời gian đóng hộp (8 h) + * Đầu vào: Product Backlog + * Đầu ra: Sprint Backlog - * Chia thành hai khoảng thời gian bổ sung 4 h: + * Chia làm 2 ô thời gian bổ sung 4h: - * Chủ sở hữu sản phẩm trình bày [Danh sách sản phẩm]({{opSetting:base_url}}/projects/your-scrum-project/backlogs) và các ưu tiên cho nhóm và giải thích Mục tiêu Sprint, mà nhóm phải đồng ý. Cùng nhau, họ ưu tiên các chủ đề từ Danh sách sản phẩm mà nhóm sẽ xử lý trong Sprint tiếp theo. Nhóm cam kết thực hiện các mục tiêu đã thảo luận. - * Nhóm lập kế hoạch độc lập (không có Chủ sở hữu sản phẩm) chi tiết và phân tách các nhiệm vụ từ các yêu cầu đã thảo luận để củng cố một [Danh sách Sprint]({{opSetting:base_url}}/projects/your-scrum-project/backlogs). + * Chủ sản phẩm trình bày [Product Backlog]({{opSetting:base_url}}/projects/your-scrum-project/backlogs) và các ưu tiên cho nhóm, đồng thời giải thích Mục tiêu Sprint mà nhóm phải đồng ý. Họ cùng nhau ưu tiên các chủ đề trong Product Backlog mà nhóm sẽ xử lý trong lần chạy nước rút tiếp theo. Nhóm cam kết thực hiện việc giao hàng đã thảo luận. + * Nhóm lập kế hoạch một cách tự động (không có Chủ sở hữu sản phẩm) một cách chi tiết và chia nhỏ các nhiệm vụ từ các yêu cầu đã thảo luận để hợp nhất [Sprint Backlog]({{opSetting:base_url}}/projects/your-scrum-project/backlogs). ### Cuộc họp Scrum hàng ngày - _Vui lòng ghi lại các chủ đề của cuộc họp Scrum hàng ngày ở đây_ + _Vui lòng ghi lại các chủ đề ở đây cho cuộc họp Scrum hàng ngày_ - * Cuộc họp trạng thái ngắn hàng ngày của nhóm. - * Giới hạn thời gian (tối đa 15 phút). - * Cuộc họp đứng để thảo luận các chủ đề sau từ [Bảng nhiệm vụ](##sprint:scrum_project__version__sprint_1). - * Tôi dự định làm gì cho đến cuộc họp Scrum tiếp theo? - * Điều gì đã cản trở công việc của tôi (Vướng mắc)? - * Scrum Master điều hành và ghi chú các [Vướng mắc Sprint](##sprint:scrum_project__version__sprint_1). - * Chủ sở hữu sản phẩm có thể tham gia để được thông báo. + * Cuộc họp ngắn, hàng ngày về tình trạng của nhóm. + * Thời gian được đóng khung (tối đa 15 phút). + * Cuộc họp độc lập để thảo luận về các chủ đề sau từ [Task board](##sprint:scrum_project__version__sprint_1). + * Tôi dự định làm gì cho đến buổi Daily Scrum tiếp theo? + * Điều gì đã cản trở công việc của tôi (Trở ngại)? + * Scrum Master kiểm duyệt và ghi chú [Sprint Impediments](##sprint:scrum_project__version__sprint_1). + * Chủ sở hữu sản phẩm có thể tham gia có thể tham gia để được thông tin. - ### Cuộc họp đánh giá Sprint + ### Họp sơ kết Sprint - _Vui lòng ghi lại các chủ đề của cuộc họp đánh giá Sprint ở đây_ + _Vui lòng ghi lại các chủ đề ở đây cho cuộc họp Sprint Review_ - * Giới hạn thời gian (4 h). - * Tối đa một giờ chuẩn bị cho mỗi người. - * Nhóm trình bày cho chủ sở hữu sản phẩm và các cá nhân quan tâm những gì đã đạt được trong Sprint này. - * Quan trọng: không sử dụng mô hình và PowerPoint! Chỉ nên trình bày chức năng sản phẩm hoàn thiện (Tăng cường) đã được thực hiện. + * Thời gian đóng hộp (4 h). + * Thời gian chuẩn bị tối đa một giờ cho mỗi người. + * Nhóm cho chủ sở hữu sản phẩm và những người quan tâm khác thấy những gì đã đạt được trong lần chạy nước rút này. + * Quan trọng: không có hình nộm và không có PowerPoint! Chức năng của sản phẩm vừa hoàn thiện (Tăng dần) phải được thể hiện. * Phản hồi từ Chủ sở hữu sản phẩm, các bên liên quan và những người khác là mong muốn và sẽ được đưa vào công việc tiếp theo. - * Dựa trên các chức năng đã trình bày, Chủ sở hữu sản phẩm quyết định đưa ra phiên bản này hoặc phát triển nó thêm. Khả năng này cho phép có ROI sớm. + * Dựa trên các chức năng đã được chứng minh, Chủ sở hữu sản phẩm quyết định đưa phần tăng trưởng này vào hoạt động hoặc phát triển thêm. Khả năng này cho phép ROI sớm. - ### Cuộc họp hồi tố Sprint + ### Hồi tưởng nước rút - _Vui lòng ghi lại các chủ đề của cuộc họp hồi tố Sprint ở đây_ + _Vui lòng ghi lại các chủ đề ở đây cho cuộc họp Cải tiến Sprint_ - * Giới hạn thời gian (3 h). - * Sau Cuộc họp đánh giá Sprint, sẽ được điều hành bởi Scrum Master. - * Nhóm thảo luận về Sprint: những gì đã diễn ra tốt, những gì cần cải thiện để trở nên hiệu quả hơn cho Sprint tiếp theo hoặc thậm chí vui vẻ hơn. + * Thời gian đóng khung (3 h). + * Sau Sprint Review, sẽ được Scrum Master kiểm duyệt. + * Nhóm thảo luận về lần chạy nước rút: điều gì đã diễn ra tốt đẹp, điều gì cần được cải thiện để đạt năng suất cao hơn cho lần chạy nước rút tiếp theo hoặc thậm chí có nhiều niềm vui hơn. item_3: - name: Sprint 2 + name: Chạy nước rút 2 categories: - item_0: Danh mục 1 (cần thay đổi trong cài đặt Dự án) + item_0: Loại 1 (sẽ được thay đổi trong cài đặt Dự án) queries: item_0: - name: Kế hoạch dự án + name: kế hoạch dự án item_1: - name: Product backlog + name: Sản phẩm tồn đọng item_2: - name: Sprint 1 + name: Chạy nước rút 1 item_3: name: Nhiệm vụ boards: kanban: - name: Bảng Kanban + name: bảng Kanban basic: name: Bảng nhiệm vụ lists: item_0: - name: Danh sách mong muốn + name: danh sách mong muốn item_1: name: Danh sách ngắn item_2: - name: Danh sách ưu tiên cho hôm nay + name: Danh sách ưu tiên cho ngày hôm nay item_3: - name: Không bao giờ + name: không bao giờ project-overview: widgets: item_0: @@ -313,26 +313,26 @@ vi: options: name: Bắt đầu text: | - Chúng tôi rất vui vì bạn đã tham gia! Chúng tôi khuyên bạn nên thử một vài điều để bắt đầu với OpenProject. + Chúng tôi rất vui vì bạn đã tham gia! Chúng tôi khuyên bạn nên thử một số thứ để bắt đầu trong OpenProject. _Hãy thử các bước sau:_ - 1. *Mời các thành viên mới vào dự án của bạn*: → Đi đến [Thành viên]({{opSetting:base_url}}/projects/your-scrum-project/members) trong điều hướng dự án. - 2. *Xem Danh sách sản phẩm và Danh sách Sprint của bạn*: → Đi đến [Danh sách]({{opSetting:base_url}}/projects/your-scrum-project/backlogs) trong điều hướng dự án. - 3. *Xem Bảng nhiệm vụ của bạn*: → Đi đến [Danh sách]({{opSetting:base_url}}/projects/your-scrum-project/backlogs) → Nhấp vào mũi tên phải trên Sprint → Chọn [Bảng nhiệm vụ](##sprint:scrum_project__version__sprint_1). - 4. *Tạo một gói công việc mới*: → Đi đến [Nhiệm vụ → Tạo]({{opSetting:base_url}}/projects/your-scrum-project/work_packages/new). - 5. *Tạo và cập nhật kế hoạch dự án*: → Đi đến [Kế hoạch dự án](##query:scrum_project__query__project_plan) trong điều hướng dự án. - 6. *Tạo wiki Sprint*: → Đi đến [Danh sách]({{opSetting:base_url}}/projects/your-scrum-project/backlogs) và mở wiki sprint từ menu thả xuống bên phải trong một sprint. Bạn có thể chỉnh sửa [mẫu wiki]({{opSetting:base_url}}/projects/your-scrum-project/wiki/) theo nhu cầu của bạn. - 7. *Kích hoạt các mô-đun khác*: → Đi đến [Cài đặt dự án → Mô-đun]({{opSetting:base_url}}/projects/your-scrum-project/settings/modules). + 1. *Mời thành viên mới tham gia dự án của bạn*: → Đi tới [Members]({{opSetting:base_url}}/projects/your-scrum-project/members) trong điều hướng dự án. + 2. *Xem Product backlog và Sprint backlog của bạn*: → Đi đến [Backlogs]({{opSetting:base_url}}/projects/your-scrum-project/backlogs) trong phần điều hướng dự án. + 3. *Xem bảng Nhiệm vụ của bạn*: → Đi tới [Backlogs]({{opSetting:base_url}}/projects/your-scrum-project/backlogs) → Nhấp vào mũi tên phải trên Sprint → Chọn [Task Board](##sprint:scrum_project__version__sprint_1). + 4. *Tạo gói công việc mới*: → Đi tới [Work packages → Create]({{opSetting:base_url}}/projects/your-scrum-project/work_packages/new). + 5. *Tạo và cập nhật kế hoạch dự án*: → Đi tới [Project plan](##query:scrum_project__query__project_plan) trong điều hướng dự án. + 6. *Tạo một wiki Sprint*: → Đi tới [Backlogs]({{opSetting:base_url}}/projects/your-scrum-project/backlogs) và mở wiki chạy nước rút từ menu thả xuống bên phải trong một lần chạy nước rút. Bạn có thể chỉnh sửa [wiki template]({{opSetting:base_url}}/projects/your-scrum-project/wiki/) tùy theo nhu cầu của mình. + 7. *Kích hoạt thêm các mô-đun*: → Đi tới [Project settings → Modules]({{opSetting:base_url}}/projects/your-scrum-project/settings/modules). - Tại đây bạn sẽ tìm thấy [Hướng dẫn người dùng](https://www.openproject.org/docs/user-guide/). - Hãy cho chúng tôi biết nếu bạn có bất kỳ câu hỏi nào hoặc cần hỗ trợ. Liên hệ với chúng tôi: [support[at]openproject.com](mailto:support@openproject.com). + Tại đây bạn sẽ tìm thấy [User Guides](https://www.openproject.org/docs/user-guide/) của chúng tôi. + Vui lòng cho chúng tôi biết nếu bạn có bất kỳ câu hỏi nào hoặc cần hỗ trợ. Liên hệ với chúng tôi: [support[at]openproject.com](mailto:support@openproject.com). item_5: options: - name: Work Packages + name: Gói công việc item_6: options: - name: Kế hoạch dự án + name: kế hoạch dự án work_packages: item_0: subject: Màn hình đăng nhập mới @@ -342,23 +342,23 @@ vi: subject: Trang web mới children: item_0: - subject: Biểu mẫu đăng ký bản tin + subject: Mẫu đăng ký nhận bản tin item_1: - subject: Triển khai tour sản phẩm + subject: Thực hiện chuyến tham quan sản phẩm item_2: subject: Trang đích mới children: item_0: - subject: Tạo wireframes cho trang đích mới + subject: Tạo wireframe cho trang đích mới item_3: - subject: Biểu mẫu liên hệ + subject: Mẫu liên hệ item_4: - subject: Carousel tính năng + subject: Băng chuyền tính năng children: item_0: - subject: Tạo ảnh chụp màn hình cho tour tính năng + subject: Tạo ảnh chụp màn hình cho chuyến tham quan tính năng item_5: - subject: Màu hover sai + subject: Màu di chuột sai item_6: subject: Chứng chỉ SSL item_7: @@ -385,123 +385,123 @@ vi: item_16: subject: Phát hành v2.0 wiki: | - ### Cuộc họp lập kế hoạch Sprint + ### Họp lập kế hoạch Sprint - _Vui lòng ghi lại các chủ đề của cuộc họp lập kế hoạch Sprint ở đây_ + _Vui lòng ghi lại các chủ đề ở đây cho cuộc họp lập kế hoạch Sprint_ - * Giới hạn thời gian (8 h) - * Đầu vào: Danh sách sản phẩm - * Đầu ra: Danh sách Sprint + * Thời gian đóng hộp (8 h) + * Đầu vào: Product Backlog + * Đầu ra: Sprint Backlog - * Chia thành hai khoảng thời gian bổ sung 4 h: + * Chia làm 2 ô thời gian bổ sung 4h: - * Chủ sở hữu sản phẩm trình bày [Danh sách sản phẩm]({{opSetting:base_url}}/projects/your-scrum-project/backlogs) và các ưu tiên cho nhóm và giải thích Mục tiêu Sprint, mà nhóm phải đồng ý. Cùng nhau, họ ưu tiên các chủ đề từ Danh sách sản phẩm mà nhóm sẽ xử lý trong Sprint tiếp theo. Nhóm cam kết thực hiện các mục tiêu đã thảo luận. - * Nhóm lập kế hoạch độc lập (không có Chủ sở hữu sản phẩm) chi tiết và phân tách các nhiệm vụ từ các yêu cầu đã thảo luận để củng cố một [Danh sách Sprint]({{opSetting:base_url}}/projects/your-scrum-project/backlogs). + * Chủ sản phẩm trình bày [Product Backlog]({{opSetting:base_url}}/projects/your-scrum-project/backlogs) và các ưu tiên cho nhóm, đồng thời giải thích Mục tiêu Sprint mà nhóm phải đồng ý. Họ cùng nhau ưu tiên các chủ đề trong Product Backlog mà nhóm sẽ xử lý trong lần chạy nước rút tiếp theo. Nhóm cam kết thực hiện việc giao hàng đã thảo luận. + * Nhóm lập kế hoạch một cách tự động (không có Chủ sở hữu sản phẩm) một cách chi tiết và chia nhỏ các nhiệm vụ từ các yêu cầu đã thảo luận để hợp nhất [Sprint Backlog]({{opSetting:base_url}}/projects/your-scrum-project/backlogs). ### Cuộc họp Scrum hàng ngày - _Vui lòng ghi lại các chủ đề của cuộc họp Scrum hàng ngày ở đây_ + _Vui lòng ghi lại các chủ đề ở đây cho cuộc họp Scrum hàng ngày_ - * Cuộc họp trạng thái ngắn hàng ngày của nhóm. - * Giới hạn thời gian (tối đa 15 phút). - * Cuộc họp đứng để thảo luận các chủ đề sau từ Bảng nhiệm vụ. - * Tôi dự định làm gì cho đến cuộc họp Scrum tiếp theo? - * Điều gì đã cản trở công việc của tôi (Vướng mắc)? - * Scrum Master điều hành và ghi chú các Vướng mắc Sprint. - * Chủ sở hữu sản phẩm có thể tham gia để được thông báo. + * Cuộc họp ngắn, hàng ngày về tình trạng của nhóm. + * Thời gian được đóng khung (tối đa 15 phút). + * Họp độc lập để thảo luận các chủ đề sau từ Ban công tác. + * Tôi dự định làm gì cho đến buổi Daily Scrum tiếp theo? + * Điều gì đã cản trở công việc của tôi (Trở ngại)? + * Scrum Master kiểm duyệt và ghi lại các Trở ngại trong Sprint. + * Chủ sở hữu sản phẩm có thể tham gia có thể tham gia để được thông tin. - ### Cuộc họp đánh giá Sprint + ### Họp sơ kết Sprint - _Vui lòng ghi lại các chủ đề của cuộc họp đánh giá Sprint ở đây_ + _Vui lòng ghi lại các chủ đề ở đây cho cuộc họp Sprint Review_ - * Giới hạn thời gian (4 h). - * Tối đa một giờ chuẩn bị cho mỗi người. - * Nhóm trình bày cho chủ sở hữu sản phẩm và các cá nhân quan tâm những gì đã đạt được trong Sprint này. - * Quan trọng: không sử dụng mô hình và PowerPoint! Chỉ nên trình bày chức năng sản phẩm hoàn thiện (Tăng cường) đã được thực hiện. + * Thời gian đóng hộp (4 h). + * Thời gian chuẩn bị tối đa một giờ cho mỗi người. + * Nhóm cho chủ sở hữu sản phẩm và những người quan tâm khác thấy những gì đã đạt được trong lần chạy nước rút này. + * Quan trọng: không có hình nộm và không có PowerPoint! Chức năng của sản phẩm vừa hoàn thiện (Tăng dần) phải được thể hiện. * Phản hồi từ Chủ sở hữu sản phẩm, các bên liên quan và những người khác là mong muốn và sẽ được đưa vào công việc tiếp theo. - * Dựa trên các chức năng đã trình bày, Chủ sở hữu sản phẩm quyết định đưa ra phiên bản này hoặc phát triển nó thêm. Khả năng này cho phép có ROI sớm. + * Dựa trên các chức năng đã được chứng minh, Chủ sở hữu sản phẩm quyết định đưa phần tăng trưởng này vào hoạt động hoặc phát triển thêm. Khả năng này cho phép ROI sớm. - ### Cuộc họp hồi tố Sprint + ### Hồi tưởng nước rút - _Vui lòng ghi lại các chủ đề của cuộc họp hồi tố Sprint ở đây_ + _Vui lòng ghi lại các chủ đề ở đây cho cuộc họp Cải tiến Sprint_ - * Giới hạn thời gian (3 h). - * Sau Cuộc họp đánh giá Sprint, sẽ được điều hành bởi Scrum Master. - * Nhóm thảo luận về Sprint: những gì đã diễn ra tốt, những gì cần cải thiện để trở nên hiệu quả hơn cho Sprint tiếp theo hoặc thậm chí vui vẻ hơn. + * Thời gian đóng khung (3 h). + * Sau Sprint Review, sẽ được Scrum Master kiểm duyệt. + * Nhóm thảo luận về lần chạy nước rút: điều gì đã diễn ra tốt đẹp, điều gì cần được cải thiện để đạt năng suất cao hơn cho lần chạy nước rút tiếp theo hoặc thậm chí có nhiều niềm vui hơn. statuses: item_0: name: Mới item_1: - name: Trong quá trình đặc tả + name: Trong đặc điểm kỹ thuật item_2: name: được chỉ định item_3: - name: Đã xác nhận + name: xác nhận item_4: - name: Sẽ được xếp lịch + name: Để được lên lịch item_5: - name: Đã xếp lịch + name: theo lịch trình item_6: name: Đang xử lý item_7: name: Đã phát triển item_8: - name: Trong quá trình thử nghiệm + name: Trong thử nghiệm item_9: - name: Đã kiểm tra + name: Đã thử nghiệm item_10: - name: Kiểm tra thất bại + name: Kiểm tra không thành công item_11: - name: Đã đóng + name: đóng cửa item_12: name: Đang chờ item_13: name: Đã từ chối time_entry_activities: item_0: - name: Quản lý + name: quản lý item_1: name: Đặc điểm kỹ thuật item_2: - name: Phát triển + name: phát triển item_3: - name: Thử nghiệm + name: thử nghiệm item_4: - name: Hỗ trợ + name: hỗ trợ item_5: - name: Khác + name: khác types: item_0: name: Nhiệm vụ item_1: - name: Milestone + name: cột mốc quan trọng item_2: - name: Summary task + name: Nhiệm vụ tóm tắt item_3: - name: Tính năng + name: tính năng item_4: name: Sử thi item_5: - name: Câu chuyện người dùng + name: Cốt truyện người dùng item_6: - name: Lỗi + name: lỗi welcome: title: Chào mừng đến với OpenProject! text: | - OpenProject là phần mềm quản lý dự án mã nguồn mở hàng đầu. Nó hỗ trợ quản lý dự án theo cách truyền thống, Agile cũng như kết hợp và cung cấp cho bạn toàn quyền kiểm soát dữ liệu của mình. + OpenProject là phần mềm quản lý dự án nguồn mở hàng đầu. Nó hỗ trợ quản lý dự án cổ điển, linh hoạt cũng như kết hợp và cung cấp cho bạn toàn quyền kiểm soát dữ liệu của mình. - Các tính năng và trường hợp sử dụng chính: + Các tính năng cốt lõi và trường hợp sử dụng: - * [Quản lý danh mục dự án](https://www.openproject.org/collaboration-software-features/project-portfolio-management/) - * [Lập kế hoạch và lên lịch dự án](https://www.openproject.org/collaboration-software-features/project-planning-scheduling/) - * [Quản lý nhiệm vụ và theo dõi sự cố](https://www.openproject.org/collaboration-software-features/task-management/) - * [Bảng Agile (Scrum và Kanban)](https://www.openproject.org/collaboration-software-features/agile-project-management/) - * [Quản lý yêu cầu và lập kế hoạch phát hành](https://www.openproject.org/collaboration-software-features/product-development/) - * [Theo dõi thời gian và chi phí, Ngân sách](https://www.openproject.org/collaboration-software-features/time-tracking/) - * [Hợp tác nhóm và Tài liệu](https://www.openproject.org/collaboration-software-features/team-collaboration/) + * [Project Portfolio Management](https://www.openproject.org/collaboration-software-features/project-portfolio-management/) + * [Project Planning and Scheduling](https://www.openproject.org/collaboration-software-features/project-planning-scheduling/) + * [Task Management and Issue Tracking](https://www.openproject.org/collaboration-software-features/task-management/) + * [Agile Boards (Scrum and Kanban)](https://www.openproject.org/collaboration-software-features/agile-project-management/) + * [Requirements Management and Release Planning](https://www.openproject.org/collaboration-software-features/product-development/) + * [Time and Cost Tracking, Budgets](https://www.openproject.org/collaboration-software-features/time-tracking/) + * [Team Collaboration and Documentation](https://www.openproject.org/collaboration-software-features/team-collaboration/) Chào mừng bạn đến với tương lai của quản lý dự án. - Đối với quản trị viên: Bạn có thể thay đổi văn bản chào mừng này [tại đây]({{opSetting:base_url}}/admin/settings/general). + Dành cho Quản trị viên: Bạn có thể thay đổi văn bản chào mừng này [here]({{opSetting:base_url}}/admin/settings/general). diff --git a/config/locales/crowdin/vi.yml b/config/locales/crowdin/vi.yml index 64e3a070ba7..8e8b706ac63 100644 --- a/config/locales/crowdin/vi.yml +++ b/config/locales/crowdin/vi.yml @@ -26,28 +26,28 @@ vi: no_results_title_text: Đã không có bất kỳ hoạt động nào cho dự án trong khung thời gian này. work_packages: activity_tab: - no_results_title_text: Không hiện hoạt động nào - no_results_description_text: 'Chọn "Hiện mọi thứ" để hiện tất cả các hoạt động và bình luận' - label_activity_show_all: "Hiện mọi thứ" - label_activity_show_only_comments: "Chỉ hiện bình luận" - label_activity_show_only_changes: "Chỉ hiện thay đổi" - label_sort_asc: "Newest at the bottom" - label_sort_desc: "Newest on top" - label_type_to_comment: "Add a comment. Type @ to notify people." - label_submit_comment: "Submit comment" - label_who: Who? - changed_on: "changed on" - created_on: "created this on" - changed: "changed" - created: "created" - commented: "commented" - internal_comment: ghi chú nội bộ - internal_journal: Internal comments are visible to a limited group of members. - unsaved_changes_confirmation_message: You have unsaved changes. Are you sure you want to close the editor? + no_results_title_text: Không có hoạt động nào để hiển thị + no_results_description_text: 'Chọn "Hiển thị mọi thứ" để hiển thị tất cả hoạt động và nhận xét' + label_activity_show_all: "Hiển thị mọi thứ" + label_activity_show_only_comments: "Chỉ hiển thị nhận xét" + label_activity_show_only_changes: "Chỉ hiển thị các thay đổi" + label_sort_asc: "Mới nhất ở phía dưới" + label_sort_desc: "Mới nhất ở trên cùng" + label_type_to_comment: "Thêm một bình luận. Nhập @ để thông báo cho mọi người." + label_submit_comment: "Gửi bình luận" + label_who: Ai? + changed_on: "đã thay đổi" + created_on: "đã tạo cái này trên" + changed: "đã thay đổi" + created: "đã tạo" + commented: "Đã bình luận" + internal_comment: Bình luận nội bộ + internal_journal: Nhận xét nội bộ được hiển thị cho một nhóm thành viên hạn chế. + unsaved_changes_confirmation_message: Bạn có những thay đổi chưa được lưu. Bạn có chắc chắn muốn đóng trình chỉnh sửa không? internal_comment_confirmation: - title: "Make this comment public?" - heading: "Make this comment public?" - description: "Your comment will be visible to anyone who can access this work package. Are you sure you want to do this?" + title: "Công khai nhận xét này?" + heading: "Công khai nhận xét này?" + description: "Nhận xét của bạn sẽ hiển thị với bất kỳ ai có thể truy cập gói công việc này. Bạn có chắc chắn muốn làm điều này?" confirm_button_text: "Công khai" admin: plugins: @@ -56,174 +56,174 @@ vi: custom_styles: color_theme: "Màu sắc giao diện" color_theme_custom: "(Tùy chỉnh)" - tab_interface: "Interface" - tab_branding: "Branding" - tab_pdf_export_styles: "PDF export styles" - tab_pdf_export_font: "PDF export font" + tab_interface: "Giao diện" + tab_branding: "Xây dựng thương hiệu" + tab_pdf_export_styles: "Kiểu xuất PDF" + tab_pdf_export_font: "Phông chữ xuất PDF" fonts: - file_too_large: "is too large (maximum size is %{count} MB)." - file_is_invalid: "is not a valid TTF font file." + file_too_large: "quá lớn (kích thước tối đa là %{count} MB)." + file_is_invalid: "không phải là tệp phông chữ TTF hợp lệ." colors: primary-button-color: "Nút chính" - accent-color: "Nhấn mạnh" + accent-color: "Màu nhấn" header-bg-color: "Nền của tiêu đề" main-menu-bg-color: "Nền menu chính" main-menu-bg-selected-background: "Menu chính khi được chọn" custom_colors: "Tùy chỉnh màu sắc" manage_colors: "Chỉnh sửa màu sắc trong select option" instructions: - primary-button-color: "Màu nhấn mạnh mạnh, được sử dụng cho nút quan trọng nhất trên màn hình." - accent-color: "Màu cho các liên kết và các yếu tố được nhấn mạnh nhẹ nhàng khác." + primary-button-color: "Màu nhấn mạnh, được sử dụng cho nút quan trọng nhất trên màn hình." + accent-color: "Màu sắc cho các liên kết và các yếu tố được đánh dấu rõ ràng khác." main-menu-bg-color: "Màu nền của trình đơn ở bên trái." theme_warning: Thay đổi chủ đề sẽ ghi đè phong cách tùy chỉnh của bạn. Thiết kế sau đó sẽ bị mất. Bạn có chắc chắn muốn tiếp tục không? enterprise: delete_dialog: - title: "Delete enterprise token" - heading: "Delete this enterprise token?" - confirmation: "Are you sure you want to delete this Enterprise edition support token?" + title: "Xóa mã thông báo doanh nghiệp" + heading: "Xóa mã thông báo doanh nghiệp này?" + confirmation: "Bạn có chắc chắn muốn xóa mã thông báo hỗ trợ phiên bản Doanh nghiệp này không?" create_dialog: - title: "Add Enterprise token" - type_token_text: "Type support token text" - token_placeholder: "Paste your Enterprise edition support token here" - add_token: "Tải lên mã hỗ trợ phiên bản Enterprise" + title: "Thêm mã thông báo doanh nghiệp" + type_token_text: "Nhập văn bản mã thông báo hỗ trợ" + token_placeholder: "Dán mã thông báo hỗ trợ phiên bản Enterprise của bạn vào đây" + add_token: "Tải lên mã thông báo hỗ trợ phiên bản Enterprise" replace_token: "Thay thế support token hiện tại của bạn" - order: "Đặt hàng phiên bản Enterprise on-premises" - paste: "Dán mã hỗ trợ phiên bản Enterprise của bạn" - required_for_feature: "Tiện ích này chỉ có sẵn với mã hỗ trợ phiên bản Enterprise đang hoạt động." + order: "Đặt mua phiên bản tại chỗ của Enterprise" + paste: "Dán mã thông báo hỗ trợ phiên bản Enterprise của bạn" + required_for_feature: "Tiện ích bổ sung này chỉ khả dụng với mã thông báo hỗ trợ phiên bản Doanh nghiệp đang hoạt động." enterprise_link: "Thêm thông tin, nhấn vào đây." start_trial: "Bắt đầu dùng thử miễn phí" book_now: "Đặt chỗ ngay" get_quote: "Lấy trích dẫn" buttons: - upgrade: "Nâng cấp ngay" - contact: "Liên hệ với chúng tôi để được trình diễn" + upgrade: "Nâng cấp ngay bây giờ" + contact: "Liên hệ với chúng tôi để có bản demo" status: - expired: "Expired" - expiring_soon: "Expiring soon" - in_grace_period: "In grace period" - invalid_domain: "Invalid domain" - not_active: "Not active" - trial: "Trial" - jemalloc_allocator: Jemalloc memory allocator + expired: "hết hạn" + expiring_soon: "Sắp hết hạn" + in_grace_period: "Đang trong thời gian ân hạn" + invalid_domain: "Tên miền không hợp lệ" + not_active: "Không hoạt động" + trial: "thử nghiệm" + jemalloc_allocator: Bộ cấp phát bộ nhớ Jemalloc journal_aggregation: explanation: - text: "Các hành động cá nhân của người dùng (ví dụ: cập nhật một công việc hai lần) được gộp thành một hành động nếu thời gian chênh lệch giữa chúng nhỏ hơn khoảng thời gian được chỉ định. Chúng sẽ được hiển thị dưới dạng một hành động duy nhất trong ứng dụng. Điều này cũng sẽ trì hoãn thông báo cùng một khoảng thời gian giảm số lượng email được gửi và cũng sẽ ảnh hưởng đến độ trễ %{webhook_link}." + text: "Các hành động riêng lẻ của người dùng (ví dụ: cập nhật gói công việc hai lần) được tổng hợp thành một hành động nếu chênh lệch tuổi tác của họ nhỏ hơn khoảng thời gian đã chỉ định. Chúng sẽ được hiển thị dưới dạng một hành động duy nhất trong ứng dụng. Điều này cũng sẽ làm chậm thông báo với cùng một khoảng thời gian làm giảm số lượng email được gửi và cũng sẽ ảnh hưởng đến độ trễ %{webhook_link}." link: "webhook" mcp_configurations: index: - description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." - resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." - resources_submit: "Update resources" - tools_heading: "Tools" - tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." - tools_submit: "Update tools" + description: "Giao thức bối cảnh mô hình cho phép các tác nhân AI cung cấp cho người dùng của mình các công cụ và tài nguyên được cung cấp bởi phiên bản OpenProject này." + resources_heading: "Tài nguyên" + resources_description: "OpenProject cung cấp các công cụ sau đây. Mỗi công cụ có thể được kích hoạt, đổi tên và mô tả theo ý muốn của bạn. Để biết thêm thông tin, vui lòng tham khảo [tài liệu về tài nguyên MCP](docs_url)." + resources_submit: "Cập nhật tài nguyên" + tools_heading: "Công cụ" + tools_description: "OpenProject cung cấp các công cụ sau đây. Mỗi công cụ có thể được kích hoạt, đổi tên và mô tả theo ý muốn của bạn. Để biết thêm thông tin, vui lòng tham khảo [tài liệu về các công cụ MCP](docs_url)." + tools_submit: "Cập nhật công cụ" multi_update: - success: "MCP configurations were updated successfully." + success: "Cấu hình MCP đã được cập nhật thành công." server_form: - description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + description_caption: "Cách máy chủ MCP sẽ được mô tả cho các ứng dụng khác kết nối với nó." + title_caption: "Một tiêu đề ngắn được hiển thị cho các ứng dụng kết nối với máy chủ MCP." update: - failure: "MCP configuration could not be updated." - success: "MCP configuration was updated successfully." + failure: "Không thể cập nhật cấu hình MCP." + success: "Cấu hình MCP đã được cập nhật thành công." scim_clients: authentication_methods: - sso: "JWT from identity provider" - oauth2_client: "OAuth 2.0 client credentials" - oauth2_token: "Static access token" + sso: "JWT từ nhà cung cấp danh tính" + oauth2_client: "Thông tin xác thực ứng dụng khách OAuth 2.0" + oauth2_token: "Mã thông báo truy cập tĩnh" created_client_credentials_dialog_component: - title: "Client credentials created" - heading: "Client credentials have been generated" - one_time_hint: "This is the only time you will see the client secret. Make sure to copy it now." + title: "Đã tạo thông tin xác thực của khách hàng" + heading: "Thông tin xác thực của khách hàng đã được tạo" + one_time_hint: "Đây là lần duy nhất bạn sẽ nhìn thấy bí mật của khách hàng. Hãy chắc chắn để sao chép nó ngay bây giờ." created_token_dialog_component: - title: "Token created" - heading: "A token has been generated" - label_token: "Token" - one_time_hint: "This is the only time you will see this token. Make sure to copy it now." + title: "Đã tạo mã thông báo" + heading: "Một mã thông báo đã được tạo" + label_token: "mã thông báo" + one_time_hint: "Đây là lần duy nhất bạn sẽ thấy mã thông báo này. Hãy chắc chắn để sao chép nó ngay bây giờ." delete_scim_client_dialog_component: - title: "Delete SCIM client" - heading: "Are you sure you want to delete this SCIM client?" - description: "Users managed by this SCIM client can no longer be updated by it." + title: "Xóa ứng dụng khách SCIM" + heading: "Bạn có chắc chắn muốn xóa ứng dụng khách SCIM này không?" + description: "Người dùng được quản lý bởi ứng dụng khách SCIM này không thể được nó cập nhật nữa." edit: - label_delete_scim_client: "Delete SCIM client" + label_delete_scim_client: "Xóa ứng dụng khách SCIM" form: - auth_provider_description: "This is the service that users added by the SCIM provider will use to authenticate in OpenProject." - authentication_method_description_html: "This is how the SCIM client authenticates at OpenProject. Please ensure that OAuth tokens include the scim_v2 scope." - description: "Please refer to our [documentation on configuring SCIM clients](docs_url) for more information on these configuration options." - jwt_sub_description: "For example, for Keycloak, this is the UUID of the service account associated with the SCIM client. Consult [our documentation](docs_url) to learn how to find the subject claim for your use case." - name_description: "Choose a name that will help other admins better understand why this client was configured." + auth_provider_description: "Đây là dịch vụ mà người dùng được nhà cung cấp SCIM thêm vào sẽ sử dụng để xác thực trong OpenProject." + authentication_method_description_html: "Đây là cách ứng dụng khách SCIM xác thực tại OpenProject. Vui lòng đảm bảo rằng mã thông báo OAuth bao gồm phạm vi scim_v2." + description: "Vui lòng tham khảo [documentation on configuring SCIM clients](docs_url) của chúng tôi để biết thêm thông tin về các tùy chọn cấu hình này." + jwt_sub_description: "Ví dụ: đối với Keycloak, đây là UUID của tài khoản dịch vụ được liên kết với máy khách SCIM. Hãy tham khảo [our documentation](docs_url) để tìm hiểu cách tìm xác nhận quyền sở hữu chủ đề cho trường hợp sử dụng của bạn." + name_description: "Chọn một tên sẽ giúp các quản trị viên khác hiểu rõ hơn lý do tại sao ứng dụng khách này được định cấu hình." index: - description: "SCIM clients configured here are able to interact with OpenProject SCIM server API to provision, update, and deprovision user accounts and groups." - label_create_button: "Add SCIM client" + description: "Các máy khách SCIM được định cấu hình ở đây có thể tương tác với API máy chủ OpenProject SCIM để cung cấp, cập nhật và hủy cấp phép các tài khoản và nhóm người dùng." + label_create_button: "Thêm ứng dụng khách SCIM" new: - title: "New SCIM client" + title: "Khách hàng SCIM mới" revoke_static_token_dialog_component: confirm_button: "Thu hồi" - title: "Revoke static token" - heading: "Bạn có chắc chắn muốn thu hồi mã truy cập này không?" - description: "The SCIM client that uses this token will no longer be able to access OpenProject's SCIM server API." + title: "Thu hồi mã thông báo tĩnh" + heading: "Bạn có chắc chắn muốn thu hồi mã thông báo này không?" + description: "Ứng dụng khách SCIM sử dụng mã thông báo này sẽ không thể truy cập API máy chủ SCIM của OpenProject nữa." table_component: blank_slate: - title: "No SCIM clients configured yet" - description: "Add clients to see them here" - user_count: "Người dùng" + title: "Chưa có máy khách SCIM nào được định cấu hình" + description: "Thêm khách hàng để xem họ ở đây" + user_count: "người dùng" token_list_component: - description: "The tokens you generate here can be passed by a SCIM client to access the OpenProject SCIM API." - heading: "Tokens" - label_add_token: "Token" - label_aria_add_token: "Add token" + description: "Mã thông báo bạn tạo ở đây có thể được máy khách SCIM chuyển để truy cập API OpenProject SCIM." + heading: "mã thông báo" + label_add_token: "mã thông báo" + label_aria_add_token: "Thêm mã thông báo" token_table_component: blank_slate: - title: "No tokens have been created yet" - description: "You can create one now" - expired: "Expired on %{date}" - revoked: "Revoked on %{date}" - title: "Access token table" + title: "Chưa có mã thông báo nào được tạo" + description: "Bạn có thể tạo một cái ngay bây giờ" + expired: "Đã hết hạn vào %{date}" + revoked: "Đã thu hồi vào %{date}" + title: "Bảng mã thông báo truy cập" settings: new_project: - project_creation: "Project creation" + project_creation: "Tạo dự án" notification_text_default: > -

    Hello,

    A new project has been created: projectValue:name

    Thank you

    +

    Xin chào,

    Một dự án mới đã được tạo: projectValue:name

    Thank you

    workflows: tabs: - default_transitions: "Default transitions" - user_author: "User is author" - user_assignee: "User is assignee" + default_transitions: "Chuyển tiếp mặc định" + user_author: "Người dùng là tác giả" + user_assignee: "Người dùng là người được chuyển nhượng" authentication: - login_and_registration: "Login and registration" + login_and_registration: "Đăng nhập và đăng ký" announcements: show_until: Xem đến khi is_active: Đang hiển thị is_inactive: hiện tại không hiển thị antivirus_scan: - not_processed_yet_message: "Tải xuống bị chặn, vì tệp chưa được quét virus. Vui lòng thử lại sau." - quarantined_message: "Phát hiện virus trong tệp '%{filename}'. Tệp đã bị cách ly và không thể tải xuống." - deleted_message: "Phát hiện virus trong tệp '%{filename}'. Tệp đã bị xóa." - deleted_by_admin: "Tệp cách ly '%{filename}' đã bị quản trị viên xóa." - overridden_by_admin: "Lệnh cách ly cho tệp '%{filename}' đã bị %{user} hủy bỏ. Tệp hiện có thể truy cập được." + not_processed_yet_message: "Quá trình tải xuống bị chặn do tệp chưa được quét vi-rút. Vui lòng thử lại sau." + quarantined_message: "Đã phát hiện thấy vi-rút trong tệp '%{filename}'. Nó đã bị cách ly và không có sẵn để tải xuống." + deleted_message: "Đã phát hiện thấy vi-rút trong tệp '%{filename}'. Tập tin đã bị xóa." + deleted_by_admin: "Tệp bị cách ly '%{filename}' đã bị quản trị viên xóa." + overridden_by_admin: "Việc cách ly tệp '%{filename}' đã bị %{user} xóa. Bây giờ tập tin có thể được truy cập." quarantined_attachments: - container: "Bộ chứa" - delete: "Xóa tệp cách ly" - title: "Tệp đính kèm cách ly" - error_cannot_act_self: "Không thể thực hiện hành động trên các tệp bạn đã tải lên." + container: "container" + delete: "Xóa tập tin bị cách ly" + title: "Tệp đính kèm được cách ly" + error_cannot_act_self: "Không thể thực hiện hành động trên các tệp đã tải lên của riêng bạn." attribute_help_texts: - caption: "This short version will be displayed as caption of the attribute." - note_public: "Any text and images you add to this field are publicly visible to all logged in users." + caption: "Phiên bản ngắn này sẽ được hiển thị dưới dạng chú thích của thuộc tính." + note_public: "Mọi văn bản và hình ảnh bạn thêm vào trường này đều hiển thị công khai với tất cả người dùng đã đăng nhập." text_overview: "Tại đây, bạn có thể tạo các văn bản hiển thị hỗ trợ. Khi hoàn thành, các văn bản có thể được hiển thị bằng cách nhấp vào biểu tượng trợ giúp bên cạnh các giá trị." show_preview: "Văn bản xem trước" add_new: "Thêm văn bản trợ giúp" - edit_field_name: "Edit help text for %{attribute_field_name}" + edit_field_name: "Chỉnh sửa văn bản trợ giúp cho %{attribute_field_name}" background_jobs: status: - error_requeue: "Công việc gặp lỗi nhưng đang thử lại. Lỗi là: %{message}" + error_requeue: "Công việc đã gặp lỗi nhưng đang thử lại. Lỗi là: %{message}" cancelled_due_to: "Công việc bị hủy do lỗi: %{message}" ldap_auth_sources: ldap_error: "Lỗi LDAP: %{error_message}" - ldap_auth_failed: "Không thể xác thực tại máy chủ LDAP." + ldap_auth_failed: "Không thể xác thực tại Máy chủ LDAP." sync_failed: "Không thể đồng bộ hóa từ LDAP: %{message}." - back_to_index: "Nhấp vào đây để quay lại danh sách kết nối." + back_to_index: "Bấm vào đây để quay lại danh sách kết nối." technical_warning: | - This LDAP form requires technical knowledge of your LDAP / Active Directory setup. + Biểu mẫu LDAP này yêu cầu kiến ​​thức kỹ thuật về thiết lập LDAP/Active Directory của bạn. [Please visit our documentation for detailed instructions](docs_url). attribute_texts: name: Tên tùy ý của kết nối LDAP @@ -232,55 +232,55 @@ vi: generic_map: "84/5000\nKhóa thuộc tính trong LDAP được ánh xạ tới thuộc tính `%{attribute}` của OpenProject" admin_map_html: "Tùy chọn: Khóa thuộc tính trong LDAP mà nếu có đánh dấu người dùng OpenProject là quản trị viên. Để trống khi nghi ngờ." system_user_dn_html: | - Enter the DN of the system user used for read-only access. + Nhập DN của người dùng hệ thống được sử dụng để truy cập chỉ đọc.
    - Example: uid=openproject,ou=system,dc=example,dc=com + Ví dụ: uid=openproject,ou=system,dc=example,dc=com system_user_password: Nhập mật khẩu liên kết của người dùng hệ thống base_dn: | Nhập Base DN của cây con trong LDAP mà bạn muốn OpenProject tìm kiếm người dùng và nhóm. - OpenProject sẽ chỉ lọc các tên người dùng được cung cấp trong cây con này. + OpenProject sẽ chỉ lọc tên người dùng được cung cấp trong cây con này. Ví dụ: ou=users,dc=example,dc=com filter_string: | - Thêm bộ lọc RFC4515 tùy chọn để áp dụng cho các kết quả trả về cho người dùng được lọc trong LDAP. - Điều này có thể được sử dụng để giới hạn tập hợp người dùng được OpenProject tìm thấy để xác thực và đồng bộ hóa nhóm. + Thêm bộ lọc RFC4515 tùy chọn để áp dụng cho kết quả trả về cho người dùng được lọc trong LDAP. + Điều này có thể được sử dụng để hạn chế nhóm người dùng được OpenProject tìm thấy để xác thực và đồng bộ hóa nhóm. filter_string_concat: | OpenProject sẽ luôn lọc thuộc tính đăng nhập do người dùng cung cấp để xác định bản ghi. Nếu bạn cung cấp bộ lọc ở đây, - nó sẽ được kết hợp với một AND. Theo mặc định, một bộ lọc catch-all (objectClass=*) sẽ được sử dụng. + nó sẽ được nối với AND. Theo mặc định, tổng hợp (objectClass=*) sẽ được sử dụng làm bộ lọc. onthefly_register: | - Nếu bạn chọn hộp này, OpenProject sẽ tự động tạo người dùng mới từ các mục nhập LDAP của họ - khi họ lần đầu tiên xác thực với OpenProject. - Để trống để chỉ cho phép các tài khoản hiện có trong OpenProject xác thực thông qua LDAP! + Nếu bạn chọn hộp này, OpenProject sẽ tự động tạo người dùng mới từ các mục LDAP của họ + khi họ xác thực lần đầu bằng OpenProject. + Bỏ chọn mục này để chỉ cho phép các tài khoản hiện có trong OpenProject xác thực thông qua LDAP! connection_encryption: "Mã hóa kết nối" encryption_details: "Tùy chọn LDAPS / STARTTLS" system_account: "Tài khoản hệ thống" system_account_legend: | - OpenProject yêu cầu truy cập chỉ đọc thông qua tài khoản hệ thống để tra cứu người dùng và nhóm trong cây LDAP của bạn. - Vui lòng chỉ định thông tin đăng nhập liên kết cho người dùng hệ thống đó trong phần sau. + OpenProject yêu cầu quyền truy cập chỉ đọc thông qua tài khoản hệ thống để tra cứu người dùng và nhóm trong cây LDAP của bạn. + Vui lòng chỉ định thông tin xác thực liên kết cho người dùng hệ thống đó trong phần sau. ldap_details: "Chi tiết LDAP " user_settings: "Ánh xạ thuộc tính" user_settings_legend: | Các trường sau đây liên quan đến cách người dùng được tạo trong OpenProject từ các mục nhập LDAP và - các thuộc tính LDAP nào được sử dụng để xác định các thuộc tính của người dùng OpenProject (ánh xạ thuộc tính). + thuộc tính LDAP nào được sử dụng để xác định thuộc tính của người dùng OpenProject (ánh xạ thuộc tính). tls_mode: - plain: "không" + plain: "không có" simple_tls: "LDAPS" - start_tls: "STARTTLS" - plain_description: "Mở kết nối không mã hóa đến máy chủ LDAP. Không khuyến khích cho môi trường sản xuất." - simple_tls_description: "Sử dụng LDAPS. Yêu cầu một cổng riêng trên máy chủ LDAP. Chế độ này thường bị loại bỏ, chúng tôi khuyến nghị sử dụng STARTTLS bất cứ khi nào có thể." - start_tls_description: "Gửi lệnh STARTTLS sau khi kết nối với cổng LDAP chuẩn. Được khuyến nghị cho kết nối mã hóa." + start_tls: "BẮT ĐẦU" + plain_description: "Mở kết nối không được mã hóa đến máy chủ LDAP. Không được khuyến khích cho sản xuất." + simple_tls_description: "Sử dụng LDAPS. Yêu cầu một cổng riêng trên máy chủ LDAP. Chế độ này thường không được dùng nữa, chúng tôi khuyên bạn nên sử dụng STARTTLS bất cứ khi nào có thể." + start_tls_description: "Gửi lệnh STARTTLS sau khi kết nối với cổng LDAP tiêu chuẩn. Được khuyến nghị cho các kết nối được mã hóa." section_more_info_link_html: > Phần này liên quan đến bảo mật kết nối của nguồn xác thực LDAP này. Để biết thêm thông tin, vui lòng truy cập tài liệu Net::LDAP. tls_options: verify_peer: "Xác minh chứng chỉ SSL" verify_peer_description_html: > - Enables strict SSL verification of the certificate trusted chain.
    Warning: Unchecking this option disables SSL verification of the LDAP server certificate. This exposes your connection to Man in the Middle attacks. - tls_certificate_description: "Nếu chứng chỉ máy chủ LDAP không có trong nguồn tin cậy của hệ thống này, bạn có thể thêm nó thủ công ở đây. Nhập chuỗi chứng chỉ PEM X509." + Cho phép xác minh SSL nghiêm ngặt của chuỗi chứng chỉ tin cậy.
    Cảnh báo: Việc bỏ chọn tùy chọn này sẽ tắt xác minh SSL của chứng chỉ máy chủ LDAP. Điều này làm lộ mối liên hệ của bạn với các cuộc tấn công của Man in the Middle. + tls_certificate_description: "Nếu chứng chỉ máy chủ LDAP không có trong các nguồn tin cậy của hệ thống này, bạn có thể thêm chứng chỉ đó theo cách thủ công tại đây. Nhập chuỗi chứng chỉ PEM X509." forums: show: no_results_title_text: Hiện tại không có bài viết cho diễn đàn. colors: index: - no_results_title_text: Hiện có không có màu sắc. + no_results_title_text: Hiện tại không có màu sắc. no_results_content_text: Tạo một màu mới label_new_color: "Màu mới" new: @@ -293,13 +293,13 @@ vi: label_no_color: "Không màu" label_properties: "Thuộc tính" label_really_delete_color: > - Bạn có chắc chắn muốn xóa màu sau không? Các loại sử dụng màu này sẽ không bị xóa. + Bạn có chắc chắn muốn xóa màu sau đây không? Những loại sử dụng màu này sẽ không bị xóa. custom_actions: actions: name: "Hành động" add: "Thêm hành động" assigned_to: - executing_user_value: "(Chỉ định cho người thực hiện)" + executing_user_value: "(Gán cho người dùng thực thi)" conditions: "Điều kiện" plural: "Tác vụ tùy chỉnh" new: "Hành động tùy chỉnh mới" @@ -309,166 +309,166 @@ vi: admin: custom_field_projects: is_for_all_blank_slate: - heading: Cho tất cả các dự án - description: This custom field is enabled in all projects since the "For all projects" option is checked. It cannot be deactivated for individual projects. + heading: Đối với tất cả các dự án + description: Trường tùy chỉnh này được bật trong tất cả các dự án vì tùy chọn "Dành cho tất cả dự án" được chọn. Nó không thể bị vô hiệu hóa đối với các dự án riêng lẻ. items: - actions: "Item actions" + actions: "Hành động mục" blankslate: root: - title: "Your list of items is empty" - description: "Start by adding items to the custom field of type hierarchy. Each item can be used to create a hierarchy bellow it. To navigate and create sub-items inside a hierarchy click on the created item." + title: "Danh sách các mục của bạn trống" + description: "Bắt đầu bằng cách thêm các mục vào trường tùy chỉnh của hệ thống phân cấp loại. Mỗi mục có thể được sử dụng để tạo một hệ thống phân cấp bên dưới nó. Để điều hướng và tạo các mục con bên trong hệ thống phân cấp, hãy nhấp vào mục đã tạo." item: - title: This item doesn't have any hierarchy level below - description: Add items to this list to create sub-items inside another one + title: Mục này không có bất kỳ cấp độ phân cấp nào dưới đây + description: Thêm các mục vào danh sách này để tạo các mục con bên trong một mục khác delete_dialog: - title: "Delete custom field item" - heading: "Delete custom field item?" - description: "This action will irreversibly remove the item and all its sub-items. Any assigned values will be permanently deleted. If this field is required, removing items may cause existing work packages to become invalid." + title: "Xóa mục trường tùy chỉnh" + heading: "Xóa mục trường tùy chỉnh?" + description: "Hành động này sẽ xóa mục này và tất cả các mục phụ của nó mà không thể đảo ngược được. Mọi giá trị được chỉ định sẽ bị xóa vĩnh viễn. Nếu trường này là bắt buộc, việc xóa các mục có thể khiến các gói công việc hiện có trở nên không hợp lệ." placeholder: - label: "Item label" - short: "Short name" - weight: "Weight" + label: "Nhãn mặt hàng" + short: "Tên viết tắt" + weight: "cân nặng" notice: - remember_items_and_projects: "Remember to set items and projects in the respective tabs for this custom field." + remember_items_and_projects: "Hãy nhớ đặt các mục và dự án trong các tab tương ứng cho trường tùy chỉnh này." hierarchy: subitems: - zero: no sub-items - one: 1 sub-item - other: "%{count} sub-items" + zero: không có mục phụ + one: 1 mục phụ + other: "%{count} mục phụ" role_assignment: - title: Role Assignment - description: You can automatically grant a certain project role to any user assigned to this project attribute, regardless of that user’s original role in that project. - warning: Depending on the role selected below, the user assigned to this project attribute might gain significantly more permissions than they previously had, including the ability to add new members and elevate their role. - role_field_label: "Project Role" - role_field_caption: This project role will automatically be granted to any user assigned to this project attribute + title: Phân công vai trò + description: Bạn có thể tự động cấp một vai trò dự án nhất định cho bất kỳ người dùng nào được gán cho thuộc tính dự án này, bất kể vai trò ban đầu của người dùng đó trong dự án đó là gì. + warning: Tùy thuộc vào vai trò được chọn bên dưới, người dùng được chỉ định cho thuộc tính dự án này có thể nhận được nhiều quyền hơn đáng kể so với trước đây, bao gồm khả năng thêm thành viên mới và nâng cao vai trò của họ. + role_field_label: "Vai trò dự án" + role_field_caption: Vai trò dự án này sẽ tự động được cấp cho bất kỳ người dùng nào được chỉ định cho thuộc tính dự án này review_hint: > - There are %{user_count} who are already assigned to this project attribute in various projects. They might get additional permissions and be added to projects they did not previously have access to. - review_button: Review users and permissions + Có %{user_count} người đã được gán cho thuộc tính dự án này trong nhiều dự án khác nhau. Họ có thể nhận được các quyền bổ sung và được thêm vào các dự án mà trước đây họ không có quyền truy cập. + review_button: Xem lại người dùng và quyền dialog: - title: "Overview of users and permissions" - change: Change + title: "Tổng quan về người dùng và quyền" + change: thay đổi changes: - new_member: Will be added as a member - remove_member: Will be removed as a member - gain_and_lose_role: Will lose role ‘%{old_role}’ and gain role ‘%{new_role}’ - gain_role: Will gain role ‘%{new_role}’ - lose_role: Will lose role ‘%{old_role}’ - no_change: No changes + new_member: Sẽ được thêm làm thành viên + remove_member: Sẽ bị xóa tư cách thành viên + gain_and_lose_role: Sẽ mất vai trò ‘%{old_role}’ và nhận được vai trò ‘%{new_role}’ + gain_role: Sẽ nhận được vai trò ‘%{new_role}’ + lose_role: Sẽ mất vai trò ‘%{old_role}’ + no_change: Không có thay đổi text_add_new_custom_field: > Để thêm các trường tùy chỉnh mới vào một dự án, trước tiên bạn cần tạo chúng trước khi có thể thêm vào dự án này. - is_enabled_globally: "Được bật toàn cầu" + is_enabled_globally: "Được kích hoạt trên toàn cầu" enabled_in_project: "Đã bật trong dự án" - contained_in_type: "Được chứa trong loại" + contained_in_type: "Chứa trong loại" confirm_destroy_option: "Xóa một tùy chọn sẽ xóa tất cả các sự kiện của nó (ví dụ như trong các gói công việc). Bạn có chắc bạn muốn xóa bỏ nó?" - reorder_alphabetical: "Sắp xếp lại giá trị theo thứ tự bảng chữ cái" - reorder_confirmation: "Cảnh báo: Thứ tự hiện tại của các giá trị sẽ bị mất. Tiếp tục?" - placeholder_version_select: "Work package or project selection is required first" - calculated_field_not_editable: "Non-editable attribute. This value is calculated automatically." - no_role_assigment: "No role assignment" + reorder_alphabetical: "Sắp xếp lại các giá trị theo thứ tự bảng chữ cái" + reorder_confirmation: "Cảnh báo: Thứ tự hiện tại của các giá trị sẵn có sẽ bị mất. Tiếp tục?" + placeholder_version_select: "Lựa chọn gói công việc hoặc dự án là bắt buộc trước tiên" + calculated_field_not_editable: "Thuộc tính không thể chỉnh sửa. Giá trị này được tính toán tự động." + no_role_assigment: "Không phân công vai trò" instructions: is_required: - all: "Mark the custom field as required. This will make it mandatory to fill in the field when creating new resources. Existing resources will not require a value when being updated." - project: "Required attributes need to be filled out by the user on project creation if the field is active ('For all projects' set or copying from a project/template in which the field is active). Existing projects will not require a value when being updated." + all: "Đánh dấu trường tùy chỉnh theo yêu cầu. Điều này sẽ khiến bạn bắt buộc phải điền vào trường khi tạo tài nguyên mới. Các tài nguyên hiện có sẽ không yêu cầu giá trị khi được cập nhật." + project: "Người dùng cần điền các thuộc tính bắt buộc khi tạo dự án nếu trường đang hoạt động (bộ 'Dành cho tất cả dự án' hoặc sao chép từ một dự án/mẫu trong đó trường đang hoạt động). Các dự án hiện tại sẽ không yêu cầu giá trị khi được cập nhật." is_for_all: - all: "Mark the custom field as available in all existing and new projects." - project: "Mark the attribute as available in all existing and new projects." + all: "Đánh dấu trường tùy chỉnh là có sẵn trong tất cả các dự án hiện có và mới." + project: "Đánh dấu thuộc tính là có sẵn trong tất cả các dự án hiện có và mới." multi_select: - all: "Allows the user to assign multiple values to this custom field." - project: "Allows the user to assign multiple values to this attribute." + all: "Cho phép người dùng chỉ định nhiều giá trị cho trường tùy chỉnh này." + project: "Cho phép người dùng gán nhiều giá trị cho thuộc tính này." searchable: - all: "Include the field values when using the global search functionality." - project: "Check to make this attribute available as a filter in project lists." + all: "Bao gồm các giá trị trường khi sử dụng chức năng tìm kiếm chung." + project: "Chọn để cung cấp thuộc tính này dưới dạng bộ lọc trong danh sách dự án." editable: - all: "Allow the field to be editable by users themselves." + all: "Cho phép người dùng tự chỉnh sửa trường này." admin_only: - all: "Check to make this custom field only visible to administrators. Users without admin rights will not be able to view or edit it." - project: "Check to make this attribute only visible to administrators. Users without admin rights will not be able to view or edit it." + all: "Chọn để làm cho trường tùy chỉnh này chỉ hiển thị với quản trị viên. Người dùng không có quyền quản trị viên sẽ không thể xem hoặc chỉnh sửa nó." + project: "Chọn để đặt thuộc tính này chỉ hiển thị với quản trị viên. Người dùng không có quyền quản trị viên sẽ không thể xem hoặc chỉnh sửa nó." is_filter: all: > - Allow the custom field to be used in a filter in work package views. Note that only with 'For all projects' selected, the custom field will show up in global views. + Cho phép sử dụng trường tùy chỉnh trong bộ lọc trong chế độ xem gói công việc. Lưu ý rằng chỉ khi chọn 'Dành cho tất cả dự án', trường tùy chỉnh sẽ hiển thị ở chế độ xem chung. formula: - project: "Add numeric values or type / to search for an attribute or a mathematical operator." + project: "Thêm giá trị số hoặc nhập / để tìm kiếm thuộc tính hoặc toán tử toán học." tab: no_results_title_text: Không có hiện tại không có trường tùy chỉnh. no_results_content_text: Tạo trường tùy chỉnh mới calculated_values: error_dialog: - title: "Error with Calculated value" + title: "Lỗi với giá trị được tính toán" errors: - unknown: "An unknown error occurred. Please review the formula for this Calculated value." - mathematical: "The mathematical formula leads to an error. Please review the project calculation attribute and try again." - missing_value: The attribute "%{custom_field_name}" is required by this Calculated value, but is empty. - disabled_value: The attribute "%{custom_field_name}" is required by this Calculated value, but is disabled for the project. + unknown: "Đã xảy ra lỗi không xác định. Vui lòng xem lại công thức cho giá trị được tính toán này." + mathematical: "Công thức toán học dẫn đến một lỗi. Vui lòng xem lại thuộc tính tính toán dự án và thử lại." + missing_value: Thuộc tính "%{custom_field_name}" được yêu cầu bởi giá trị được tính toán này nhưng lại trống. + disabled_value: Thuộc tính "%{custom_field_name}" được yêu cầu bởi giá trị được tính toán này nhưng bị vô hiệu hóa đối với dự án. concatenation: single: "hoặc" danger_dialog: - confirmation_live_message_checked: "The button to proceed is now active." - confirmation_live_message_unchecked: "The button to proceed is now inactive. You need to tick the checkbox to continue." + confirmation_live_message_checked: "Nút để tiếp tục hiện đang hoạt động." + confirmation_live_message_unchecked: "Nút để tiếp tục hiện không hoạt động. Bạn cần đánh dấu vào hộp kiểm để tiếp tục." op_dry_validation: or: "hoặc" errors: - array?: "must be an array." - decimal?: "must be a decimal." - defined: "must not be defined." - eql?: "must be equal to %{left}." - filled?: "must be filled." - greater_or_equal_zero: "must be greater or equal to 0." - gteq?: "must be greater than or equal to %{num}." - hash?: "must be a hash." + array?: "phải là một mảng." + decimal?: "phải là số thập phân." + defined: "không được xác định." + eql?: "phải bằng %{left}." + filled?: "phải được lấp đầy." + greater_or_equal_zero: "phải lớn hơn hoặc bằng 0." + gteq?: "phải lớn hơn hoặc bằng %{num}." + hash?: "phải là một hàm băm." included_in?: arg: - default: "must be one of: %{list}." - range: "must be one of: %{list_left} - %{list_right}." - int?: "must be an integer." - key?: "is missing." - not_found: "not found." - respond_to?: "does not implement required method." + default: "phải là một trong: %{list}." + range: "phải là một trong: %{list_left} - %{list_right}." + int?: "phải là số nguyên." + key?: "bị thiếu." + not_found: "không tìm thấy." + respond_to?: "không thực hiện phương pháp cần thiết." rules: copy_workflow_from: - workflow_missing: "has no own workflow." + workflow_missing: "không có quy trình làm việc riêng." custom_field: - format_not_supported: "format '%{field_format}' is unsupported." + format_not_supported: "định dạng '%{field_format}' không được hỗ trợ." item: - root_item: "cannot be a root item." - not_persisted: "must be an already existing item." + root_item: "không thể là một mục gốc." + not_persisted: "phải là một mục đã tồn tại." label: - not_unique: "must be unique within the same hierarchy level." + not_unique: "phải là duy nhất trong cùng một cấp bậc." short: - not_unique: "must be unique within the same hierarchy level." + not_unique: "phải là duy nhất trong cùng một cấp bậc." parent: - not_descendant: "must be a descendant of the hierarchy root." - str?: "must be a string." - type?: "must be %{type}." + not_descendant: "phải là hậu duệ của gốc phân cấp." + str?: "phải là một chuỗi." + type?: "phải là %{type}." rules: - copy_workflow_from: "Type for workflow copy" - enabled: "Đã kích hoạt" - depth: "Depth" - item: "Item" - label: "Label" - weight: "Weight" - short: "Short name" - parent: "Cha" - blueprint: "Pattern blueprint" + copy_workflow_from: "Loại bản sao quy trình công việc" + enabled: "đã bật" + depth: "Độ sâu" + item: "mục" + label: "nhãn" + weight: "cân nặng" + short: "Tên viết tắt" + parent: "cha mẹ" + blueprint: "bản thiết kế hoa văn" global_search: title: - all_projects: 'Search for "%{search_term}" in all projects' - current_project: 'Search for "%{search_term}" in %{project_name}' - project_and_subprojects: 'Search for "%{search_term}" in %{project_name} and all subprojects' + all_projects: 'Tìm kiếm "%{search_term}" trong tất cả các dự án' + current_project: 'Tìm kiếm "%{search_term}" trong %{project_name}' + project_and_subprojects: 'Tìm kiếm "%{search_term}" trong %{project_name} và tất cả các dự án con' placeholder: "Tìm kiếm trong %{app_title}" overwritten_tabs: - all: "Tất cả" - messages: "Diễn đàn" - wiki_pages: "Wiki" + all: "tất cả" + messages: "diễn đàn" + wiki_pages: "wiki" groups: edit: - synchronized_groups: "Synchronized groups" + synchronized_groups: "Nhóm được đồng bộ hóa" index: - description: By grouping users together, you can add them as members to the same projects or assign the same global roles to them. + description: Bằng cách nhóm người dùng lại với nhau, bạn có thể thêm họ làm thành viên cho cùng một dự án hoặc chỉ định các vai trò chung giống nhau cho họ. table_component: blank_slate: - description: You can define named groups of users with specific permissions. - title: No groups set up yet - user_count: User count + description: Bạn có thể xác định các nhóm người dùng được đặt tên với các quyền cụ thể. + title: Chưa có nhóm nào được thành lập + user_count: Số người dùng users: no_results_title_text: Hiện có không có người sử dụng thuộc nhóm này. memberships: @@ -476,108 +476,108 @@ vi: synchronized_groups: blankslate: action: Cài đặt xác thực - description: When this group is automatically synced with groups in external identity providers like OpenID, they will appear here. You can set this up in your Authentication settings. - title: No synchronized groups yet + description: Khi nhóm này được tự động đồng bộ hóa với các nhóm trong nhà cung cấp danh tính bên ngoài như OpenID, họ sẽ xuất hiện ở đây. Bạn có thể thiết lập tính năng này trong cài đặt Xác thực của mình. + title: Chưa có nhóm được đồng bộ hóa incoming_mails: ignore_filenames: > Chỉ định danh sách các tên cần bỏ qua khi xử lý tệp đính kèm cho thư đến (ví dụ: chữ ký hoặc biểu tượng). Nhập một tên tệp trên mỗi dòng. portfolios: index: search: - label: Portfolio name filter - placeholder: Search portfolios + label: Bộ lọc tên danh mục đầu tư + placeholder: Tìm kiếm danh mục đầu tư sub_items_html: - other: "%{count} sub-items" + other: "%{count} mục phụ" lists: - active: "Active portfolios" - my: "My portfolios" - favorited: "Favorite portfolios" - archived: "Archived portfolios" + active: "Danh mục đầu tư đang hoạt động" + my: "Danh mục đầu tư của tôi" + favorited: "Danh mục yêu thích" + archived: "Danh mục lưu trữ" projects: copy: #Contains custom strings for options when copying a project that cannot be found elsewhere. - members: "Thành viên dự án" + members: "thành viên dự án" overviews: "Tổng quan dự án" - queries: "Gói công việc: chế độ xem đã lưu" - wiki_page_attachments: "Trang wiki: tệp đính kèm" + queries: "Gói công việc: lượt xem đã lưu" + wiki_page_attachments: "Trang Wiki: tệp đính kèm" work_package_attachments: "Gói công việc: tệp đính kèm" work_package_categories: "Gói công việc: danh mục" - work_package_file_links: "Gói công việc: liên kết tệp" - work_package_shares: "Gói công việc: chia sẻ" + work_package_file_links: "Gói công việc: liên kết tập tin" + work_package_shares: "Gói công việc: cổ phiếu" create: - notification_email_subject: "Your project '%{project_name}' has been created" - complete_wizard_link: "Complete the %{artefact_name}" + notification_email_subject: "Dự án '%{project_name}' của bạn đã được tạo" + complete_wizard_link: "Hoàn thành %{artefact_name}" delete: - scheduled: "Việc xóa đã được lên lịch và thực hiện trong nền. Bạn sẽ được thông báo về kết quả." + scheduled: "Việc xóa đã được lên lịch và được thực hiện ở chế độ nền. Bạn sẽ được thông báo về kết quả." schedule_failed: "Dự án không thể bị xóa: %{errors}" - failed: "Deletion of project '%{name}' has failed" - failed_text: "The request to delete project '%{name}' has failed. The project was left archived." - completed: "Deletion of project '%{name}' completed" + failed: "Xóa dự án '%{name}' không thành công" + failed_text: "Yêu cầu xóa dự án '%{name}' không thành công. Dự án đã được lưu trữ." + completed: "Việc xóa dự án '%{name}' đã hoàn tất" completed_text: "Yêu cầu xóa dự án '%{name}' đã được hoàn thành." - completed_text_children: "Ngoài ra, các dự án phụ sau đây đã bị xóa:" + completed_text_children: "Ngoài ra, các tiểu dự án sau đã bị xóa:" index: - open_as_gantt: "Mở dưới dạng chế độ xem Gantt" + open_as_gantt: "Mở dưới dạng dạng xem Gantt" no_results_title_text: Không có dự án nào no_results_content_text: Tạo một dự án mới search: label: Bộ lọc tên dự án placeholder: Tìm kiếm theo tên dự án lists: - active: "Các dự án đang hoạt động" - my: "Dự án của tôi" - favorited: "Dự án yêu thích" - archived: "Các dự án đã lưu trữ" + active: "Dự án đang hoạt động" + my: "dự án của tôi" + favorited: "dự án yêu thích" + archived: "Dự án đã lưu trữ" shared: "Danh sách dự án được chia sẻ" my_lists: "Danh sách dự án của tôi" new: placeholder: "Danh sách dự án mới" delete_modal: title: "Xóa danh sách dự án" - heading: "Delete this project list?" - text: "Hành động này sẽ không xóa bất kỳ dự án nào có trong danh sách. Bạn có chắc chắn muốn xóa danh sách dự án này không?" + heading: "Xóa danh sách dự án này?" + text: "Hành động này sẽ không xóa bất kỳ dự án nào có trong danh sách. Bạn có chắc chắn muốn xóa danh sách dự án này?" settings: - header_details: Basic details - header_status: Status - header_relations: Project relations - button_update_details: Update details - button_update_status_description: Update status description - button_update_parent_project: Update parent project + header_details: Chi tiết cơ bản + header_status: trạng thái + header_relations: Quan hệ dự án + button_update_details: Cập nhật chi tiết + button_update_status_description: Cập nhật mô tả trạng thái + button_update_parent_project: Cập nhật dự án mẹ public_warning: > - This project is public. Anyone who has access to this instance will be able to view and interact with this project depending on their role and associated permissions. Sub-projects are not affected and have their own settings. + Dự án này là công khai. Bất kỳ ai có quyền truy cập vào phiên bản này đều có thể xem và tương tác với dự án này tùy thuộc vào vai trò và các quyền liên quan của họ. Các tiểu dự án không bị ảnh hưởng và có cài đặt riêng. public_confirmation: - checkbox: "I understand that this will make the previously private content public" - title: "Make this project public?" + checkbox: "Tôi hiểu rằng điều này sẽ công khai nội dung riêng tư trước đó" + title: "Công khai dự án này?" description: > - Anyone who has access to this instance will be able to view and interact with this project depending on their role and authentication settings. Sub-projects are not affected and have their own settings. + Bất kỳ ai có quyền truy cập vào phiên bản này đều có thể xem và tương tác với dự án này tùy thuộc vào vai trò và cài đặt xác thực của họ. Các tiểu dự án không bị ảnh hưởng và có cài đặt riêng. private_confirmation: - checkbox: "I understand that this will make the previously public content private." - title: "Make this project private?" + checkbox: "Tôi hiểu rằng điều này sẽ khiến nội dung công khai trước đó trở nên riêng tư." + title: "Đặt dự án này ở chế độ riêng tư?" description: > - The project will only be visible to project members depending on their role and associated permissions. Sub-projects are not affected and have their own settings. + Dự án sẽ chỉ hiển thị với các thành viên dự án tùy thuộc vào vai trò và quyền liên quan của họ. Các tiểu dự án không bị ảnh hưởng và có cài đặt riêng. change_identifier: Thay đổi định danh subitems: template_section: > - Select templates to be used when creating new subitems. - project_template_label: "Template for projects" - project_template_caption: "Select a template project to be used as the default for new subitems of this type." - program_template_label: "Template for programs" - program_template_caption: "Select a template program to be used as the default for new subitems of this type." - no_template: "No predefined template" + Chọn các mẫu sẽ được sử dụng khi tạo các mục con mới. + project_template_label: "Mẫu cho dự án" + project_template_caption: "Chọn một dự án mẫu sẽ được sử dụng làm mặc định cho các mục con mới thuộc loại này." + program_template_label: "Mẫu chương trình" + program_template_caption: "Chọn một chương trình mẫu sẽ được sử dụng làm mặc định cho các mục con mới thuộc loại này." + no_template: "Không có mẫu được xác định trước" template: - menu_title: "Template" - title: "Template settings" - enable_failed: "Failed to enable template mode." + menu_title: "mẫu" + title: "Cài đặt mẫu" + enable_failed: "Không bật được chế độ mẫu." members: - excluded_roles_label: "Roles to exclude when template is applied" + excluded_roles_label: "Các vai trò cần loại trừ khi áp dụng mẫu" excluded_roles_caption: > - When creating a new project from this template, the roles selected above will be omitted. This allows you to select which members will be excluded based on their project roles. Users can then access the template for viewing purposes without being granted access to new projects created from it. + Khi tạo dự án mới từ mẫu này, các vai trò đã chọn ở trên sẽ bị bỏ qua. Điều này cho phép bạn chọn thành viên nào sẽ bị loại trừ dựa trên vai trò dự án của họ. Sau đó, người dùng có thể truy cập mẫu cho mục đích xem mà không được cấp quyền truy cập vào các dự án mới được tạo từ mẫu đó. actions: - label_enable_all: "Bật tất cả" - label_disable_all: "Tắt tất cả" + label_enable_all: "Kích hoạt tất cả" + label_disable_all: "Vô hiệu hóa tất cả" activities: no_results_title_text: Không có hoạt động nào hiện tại. forums: - no_results_title_text: Hiện không có diễn đàn nào cho dự án. + no_results_title_text: Hiện tại không có diễn đàn cho dự án. no_results_content_text: Tạo diễn đàn mới categories: no_results_title_text: Hiện có không có nhóm gói công việc. @@ -586,14 +586,14 @@ vi: no_results_title_text: Không không có trường tùy chỉnh. life_cycle: header: - title: "Project life cycle" - description_html: 'The active project phases define this project''s life cycle and are defined in the administration settings. Enabled phases will be displayed in your project overview.' - non_defined: "No phases are currently defined." - section_header: "Phases" + title: "Vòng đời dự án" + description_html: 'Các giai đoạn hoạt động của dự án xác định chu kỳ sống của dự án và được định nghĩa trong cài đặt quản trị. Các giai đoạn được kích hoạt sẽ được hiển thị trong tổng quan dự án.' + non_defined: "Hiện tại không có giai đoạn nào được xác định." + section_header: "giai đoạn" step: - use_in_project: "Use %{step} in this project" + use_in_project: "Sử dụng %{step} trong dự án này" filter: - label: "Search project phase" + label: "Tìm kiếm giai đoạn dự án" project_custom_fields: header: title: "Các thuộc tính dự án" @@ -601,12 +601,12 @@ vi: filter: label: "Tìm kiếm thuộc tính dự án" actions: - label_enable_single: "Kích hoạt trong dự án này, nhấp để tắt" - label_disable_single: "Không kích hoạt trong dự án này, nhấp để bật" + label_enable_single: "Đang hoạt động trong dự án này, nhấp để tắt" + label_disable_single: "Không hoạt động trong dự án này, nhấp để bật" remove_from_project: "Xóa khỏi dự án" is_for_all_blank_slate: - heading: For all projects - description: This project attribute is enabled in all projects since the "For all projects" option is checked. It cannot be deactivated for individual projects. + heading: Đối với tất cả các dự án + description: Thuộc tính dự án này được bật trong tất cả các dự án vì tùy chọn "Dành cho tất cả dự án" được chọn. Nó không thể bị vô hiệu hóa đối với các dự án riêng lẻ. types: no_results_title_text: Không có phân loại. form: @@ -614,349 +614,347 @@ vi: versions: no_results_title_text: Hiện đang có phiên bản dự án. no_results_content_text: Tạo ra một phiên bản mới - work_packages_graph: Đồ thị gói công việc - show_work_packages: Show work packages + work_packages_graph: Biểu đồ gói công việc + show_work_packages: Hiển thị các gói công việc storage: - no_results_title_text: Không có không gian đĩa bổ sung nào được ghi nhận bởi dự án này. + no_results_title_text: Không có dung lượng đĩa ghi bổ sung nào được sử dụng bởi dự án này. work_package_priorities: - new_label: "New priority" + new_label: "Ưu tiên mới" creation_wizard: export: - description_attachment_export: "The generated artifact will be saved as a PDF attachment to the artifact work package." - description_file_link_export: "The artifact work package will have a file link to a PDF stored in an external file storage. Requires a working file storage with automatically-managed project folders for this project. At the moment only Nextcloud file storages are supported." - description_file_storage_selection: "Select which of the configured external file storages should be used." - external_file_storage: "External file storage" - label_artifact_export: "Artifact export" - label_attachment_export: "Save as work package file attachment" - label_file_link_export: "Upload file to external file storage and add file link to work package" - pdf_file_storage: "PDF file storage" - unavailable: "unavailable" - label_request_submission: "Request submission" + description_attachment_export: "Tạo tác được tạo sẽ được lưu dưới dạng tệp đính kèm PDF vào gói tác phẩm tạo tác." + description_file_link_export: "Gói công việc tạo tác sẽ có liên kết tệp tới tệp PDF được lưu trữ trong bộ lưu trữ tệp bên ngoài. Yêu cầu lưu trữ tệp đang hoạt động với các thư mục dự án được quản lý tự động cho dự án này. Hiện tại chỉ hỗ trợ lưu trữ tệp Nextcloud." + description_file_storage_selection: "Chọn kho lưu trữ tệp bên ngoài được định cấu hình sẽ được sử dụng." + external_file_storage: "Lưu trữ tập tin bên ngoài" + label_artifact_export: "Xuất khẩu hiện vật" + label_attachment_export: "Lưu dưới dạng tệp đính kèm tệp gói công việc" + label_file_link_export: "Tải tệp lên bộ lưu trữ tệp bên ngoài và thêm liên kết tệp vào gói công việc" + pdf_file_storage: "lưu trữ tập tin PDF" + unavailable: "không có sẵn" + label_request_submission: "Yêu cầu gửi" project_attributes_description: > - Select which project attributes should be included in the project initiation request. This list only includes [project attributes](project_attributes_url) enabled for for this project. + Chọn thuộc tính dự án nào sẽ được đưa vào yêu cầu khởi tạo dự án. Danh sách này chỉ bao gồm [project attributes](project_attributes_url) được bật cho dự án này. status: - button_edit: Edit status + button_edit: Chỉnh sửa trạng thái wizard: - sidebar_content_title: "Content" - sections: "Sections" - title: "Project initiation request" - no_help_text: "This attribute has no help text defined." - success: "Project attributes saved and artifact work package created successfully." - progress_label: "%{current} of %{total}" - create_artifact_work_package_error: "Failed to create artifact work package" - create_artifact_storage_error: "Failed to store artifact in file storage" + sidebar_content_title: "Nội dung" + sections: "phần" + title: "Yêu cầu khởi tạo dự án" + no_help_text: "Thuộc tính này không có văn bản trợ giúp được xác định." + success: "Thuộc tính dự án đã được lưu và gói công việc tạo tác được tạo thành công." + progress_label: "%{current} trong số %{total}" + create_artifact_work_package_error: "Không tạo được gói công việc tạo tác" + create_artifact_storage_error: "Không thể lưu trữ thành phần lạ trong bộ lưu trữ tệp" lists: create: - success: "Danh sách đã sửa đổi đã được lưu thành danh sách mới" + success: "Danh sách sửa đổi đã được lưu dưới dạng danh sách mới" failure: "Không thể lưu danh sách đã sửa đổi: %{errors}" update: - success: "Danh sách đã sửa đổi đã được lưu" + success: "Danh sách sửa đổi đã được lưu" failure: "Không thể lưu danh sách đã sửa đổi: %{errors}" publish: - success: "Danh sách đã được công bố" + success: "Danh sách đã được công khai" failure: "Danh sách không thể được công khai: %{errors}" unpublish: - success: "Danh sách đã được giữ riêng tư" - failure: "Không thể đặt danh sách ở chế độ riêng tư: %{errors}" + success: "Danh sách đã được giữ kín" + failure: "Danh sách không thể được đặt ở chế độ riêng tư: %{errors}" can_be_saved: "Danh sách đã sửa đổi:" - can_be_saved_as: "Những sửa đổi chỉ có thể được lưu trong danh sách mới:" + can_be_saved_as: "Các sửa đổi chỉ có thể được lưu trong danh sách mới:" members: index: no_results_title_text: Hiện có không có thành viên của dự án này. no_results_content_text: Thêm một thành viên vào dự án invite_by_mail: "Gửi lời mời cho %{mail}" - send_invite_to: "Gửi lời mời đến" + send_invite_to: "Gửi lời mời tới" columns: - shared: "Được chia sẻ" + shared: "đã chia sẻ" filters: - all_shares: "Tất cả các chia sẻ" + all_shares: "Tất cả các lượt chia sẻ" menu: - all: "Tất cả" + all: "tất cả" invited: "Đã mời" - locked: "Đã khóa" - project_roles: "Vai trò trong dự án" + locked: "bị khóa" + project_roles: "Vai trò dự án" wp_shares: "Chia sẻ gói công việc" - groups: "Các Nhóm" + groups: "nhóm" delete_member_dialog: title: "Xóa thành viên" - will_remove_the_users_role: "Điều này sẽ xóa vai trò của người dùng trong dự án này." - will_remove_the_groups_role: "Điều này sẽ xóa vai trò của nhóm trong dự án này." + will_remove_the_users_role: "Điều này sẽ loại bỏ vai trò của người dùng khỏi dự án này." + will_remove_the_groups_role: "Điều này sẽ loại bỏ vai trò nhóm khỏi dự án này." however_work_packages_shared_with_user_html: other: "Tuy nhiên, %{shared_work_packages_link} cũng đã được chia sẻ với người dùng này." however_work_packages_shared_with_group_html: other: "Tuy nhiên, %{shared_work_packages_link} cũng đã được chia sẻ với nhóm này." - remove_work_packages_shared_with_user_too: "Một người dùng đã bị xóa vẫn có thể truy cập các gói công việc đã được chia sẻ. Bạn có muốn xóa các chia sẻ này không?" - remove_work_packages_shared_with_group_too: "Một nhóm đã bị xóa vẫn có thể truy cập các gói công việc đã được chia sẻ. Bạn có muốn xóa các chia sẻ này không?" + remove_work_packages_shared_with_user_too: "Người dùng đã bị xóa tư cách thành viên vẫn có thể truy cập các gói công việc được chia sẻ. Bạn có muốn xóa các lượt chia sẻ không?" + remove_work_packages_shared_with_group_too: "Một nhóm đã bị loại bỏ tư cách thành viên vẫn có thể truy cập các gói công việc được chia sẻ. Bạn có muốn xóa các lượt chia sẻ không?" will_not_affect_inherited_shares: "(Điều này sẽ không ảnh hưởng đến các gói công việc được chia sẻ với nhóm của họ)." - can_remove_direct_but_not_shared_roles: "Bạn có thể xóa người dùng này khỏi tư cách thành viên dự án trực tiếp nhưng nhóm mà họ thuộc về cũng là thành viên của dự án này, vì vậy họ sẽ tiếp tục là thành viên qua nhóm." + can_remove_direct_but_not_shared_roles: "Bạn có thể xóa người dùng này với tư cách là thành viên trực tiếp của dự án nhưng nhóm mà họ tham gia cũng là thành viên của dự án này, vì vậy họ sẽ tiếp tục là thành viên thông qua nhóm." also_work_packages_shared_with_user_html: other: "Ngoài ra, %{shared_work_packages_link} đã được chia sẻ với người dùng này." - remove_project_membership_or_work_package_shares_too: "Bạn muốn chỉ xóa người dùng này khỏi tư cách thành viên trực tiếp (và giữ lại các chia sẻ) hay xóa luôn các chia sẻ gói công việc?" - will_remove_all_user_access_priveleges: "Việc xóa thành viên này sẽ xóa tất cả quyền truy cập của người dùng vào dự án. Người dùng vẫn sẽ tồn tại trong hệ thống." - will_remove_all_group_access_priveleges: "Việc xóa thành viên này sẽ xóa tất cả quyền truy cập của nhóm vào dự án. Nhóm vẫn sẽ tồn tại trong hệ thống." + remove_project_membership_or_work_package_shares_too: "Bạn chỉ muốn xóa người dùng với tư cách là thành viên trực tiếp (và giữ lại phần chia sẻ) hay xóa cả phần chia sẻ gói công việc?" + will_remove_all_user_access_priveleges: "Xóa thành viên này sẽ xóa tất cả các đặc quyền truy cập của người dùng vào dự án. Người dùng sẽ vẫn tồn tại như một phần của phiên bản." + will_remove_all_group_access_priveleges: "Xóa thành viên này sẽ xóa tất cả các đặc quyền truy cập của nhóm vào dự án. Nhóm sẽ vẫn tồn tại như một phần của phiên bản." cannot_delete_inherited_membership: "Bạn không thể xóa thành viên này vì họ thuộc về một nhóm cũng là thành viên của dự án này." - cannot_delete_inherited_membership_note_admin_html: "Bạn có thể xóa nhóm khỏi dự án hoặc xóa thành viên cụ thể này khỏi nhóm trong %{administration_settings_link}." - cannot_delete_inherited_membership_note_non_admin: "Bạn có thể xóa nhóm khỏi dự án hoặc liên hệ với quản trị viên để xóa thành viên cụ thể này khỏi nhóm." + cannot_delete_inherited_membership_note_admin_html: "Bạn có thể xóa nhóm với tư cách là thành viên của dự án hoặc thành viên cụ thể này khỏi nhóm trong %{administration_settings_link}." + cannot_delete_inherited_membership_note_non_admin: "Bạn có thể xóa nhóm với tư cách thành viên của dự án hoặc liên hệ với quản trị viên của mình để xóa thành viên cụ thể này khỏi nhóm." delete_work_package_shares_dialog: title: "Thu hồi quyền chia sẻ gói công việc" shared_with_this_user_html: - other: "Ngoài ra, %{all_shared_work_packages_link} đã được chia sẻ với người dùng này." + other: "%{all_shared_work_packages_link} đã được chia sẻ với người dùng này." shared_with_this_group_html: other: "%{all_shared_work_packages_link} đã được chia sẻ với nhóm này." shared_with_permission_html: - other: "Chỉ có %{shared_work_packages_link} được chia sẻ với quyền %{shared_role_name}." - revoke_all_or_with_role: "Bạn muốn thu hồi quyền truy cập vào tất cả các gói công việc chia sẻ, hay chỉ các gói công việc với quyền %{shared_role_name}?" + other: "Chỉ %{shared_work_packages_link} được chia sẻ với quyền %{shared_role_name}." + revoke_all_or_with_role: "Bạn muốn thu hồi quyền truy cập vào tất cả các gói công việc được chia sẻ hay chỉ những gói có quyền %{shared_role_name}?" will_not_affect_inherited_shares: "(Điều này sẽ không ảnh hưởng đến các gói công việc được chia sẻ với nhóm của họ)." - cannot_remove_inherited: "Các chia sẻ gói công việc chia sẻ qua nhóm không thể bị xóa." - cannot_remove_inherited_with_role: "Các chia sẻ gói công việc với quyền %{shared_role_name} được chia sẻ qua nhóm và không thể bị xóa." - cannot_remove_inherited_note_admin_html: "Bạn có thể thu hồi chia sẻ cho nhóm hoặc xóa thành viên cụ thể này khỏi nhóm trong %{administration_settings_link}." - cannot_remove_inherited_note_non_admin: "Bạn có thể thu hồi chia sẻ cho nhóm hoặc liên hệ với quản trị viên để xóa thành viên cụ thể này khỏi nhóm." - will_revoke_directly_granted_access: "Hành động này sẽ thu hồi quyền truy cập của họ vào tất cả các gói công việc, nhưng không ảnh hưởng đến các gói công việc chia sẻ với nhóm." - will_revoke_access_to_all: "Hành động này sẽ thu hồi quyền truy cập của họ vào tất cả các gói công việc." + cannot_remove_inherited: "Không thể xóa các gói công việc được chia sẻ qua nhóm." + cannot_remove_inherited_with_role: "Các gói công việc chia sẻ với vai trò %{shared_role_name} được chia sẻ qua các nhóm và không thể xóa được." + cannot_remove_inherited_note_admin_html: "Bạn có thể thu hồi quyền chia sẻ đối với nhóm hoặc xóa thành viên cụ thể này khỏi nhóm trong %{administration_settings_link}." + cannot_remove_inherited_note_non_admin: "Bạn có thể thu hồi quyền chia sẻ với nhóm hoặc liên hệ với quản trị viên của mình để xóa thành viên cụ thể này khỏi nhóm." + will_revoke_directly_granted_access: "Hành động này sẽ thu hồi quyền truy cập của họ vào tất cả chúng, nhưng các gói công việc được chia sẻ với một nhóm." + will_revoke_access_to_all: "Hành động này sẽ thu hồi quyền truy cập của họ vào tất cả chúng." my: access_token: dialog: token/api: - dialog_title: "Tạo token API mới" - attention_text: "Treat API tokens like passwords. Anyone with this token will have access to information from this instance, share it only with trusted users." - dialog_body: "Token này sẽ cho phép các ứng dụng bên thứ ba giao tiếp với hệ thống của bạn. Để phân biệt token API mới, vui lòng đặt tên cho nó." - create_button: "Tạo mới" - name_label: "Token name" + dialog_title: "Tạo mã thông báo API mới" + attention_text: "Hãy coi mã thông báo API như mật khẩu. Bất kỳ ai có mã thông báo này sẽ có quyền truy cập vào thông tin từ phiên bản này và chỉ chia sẻ thông tin đó với những người dùng đáng tin cậy." + dialog_body: "Mã thông báo này sẽ cho phép các ứng dụng của bên thứ ba giao tiếp với phiên bản của bạn. Để phân biệt mã thông báo API mới, vui lòng đặt tên cho nó." + create_button: "Tạo" + name_label: "Tên mã thông báo" created_dialog: - one_time_warning: "This is the only time you will see this token. Make sure to copy it now." + one_time_warning: "Đây là lần duy nhất bạn sẽ thấy mã thông báo này. Hãy chắc chắn để sao chép nó ngay bây giờ." token/api: - title: "The API token has been generated" + title: "Mã thông báo API đã được tạo" token/rss: - title: "The RSS token has been generated" + title: "Mã thông báo RSS đã được tạo" failed_to_reset_token: "Lỗi cập nhật mã truy cập: %{error}" - failed_to_create_token: "Không thể tạo token truy cập: %{error}" - failed_to_revoke_token: "Không thể thu hồi token truy cập: %{error}" + failed_to_create_token: "Không tạo được mã thông báo truy cập: %{error}" + failed_to_revoke_token: "Không thu hồi được mã thông báo truy cập: %{error}" notice_reset_token: "Một mã %{type} truy cập mới vừa được tạo. Mã truy cập của bạn là:" token_value_warning: "Chú ý: Mã này chỉ hiện duy nhất một lần, bạn nên sao chép lại để lưu trữ." no_results_title_text: "Không có thẻ truy cập hiện hữu." - notice_api_token_revoked: "Token API đã bị xóa. Để tạo token mới, vui lòng sử dụng nút trong phần API." - notice_rss_token_revoked: "Token RSS đã bị xóa. Để tạo token mới, vui lòng sử dụng liên kết trong phần RSS." - notice_ical_token_revoked: 'Token iCalendar "%{token_name}" cho lịch "%{calendar_name}" của dự án "%{project_name}" đã bị thu hồi. URL iCalendar với token này hiện không hợp lệ.' + notice_api_token_revoked: "Mã thông báo API đã bị xóa. Để tạo mã thông báo mới, vui lòng sử dụng nút trong phần API." + notice_rss_token_revoked: "Mã thông báo RSS đã bị xóa. Để tạo mã thông báo mới, vui lòng sử dụng liên kết trong phần RSS." + notice_ical_token_revoked: 'Mã thông báo iCalendar "%{token_name}" cho lịch "%{calendar_name}" của dự án "%{project_name}" đã bị thu hồi. URL iCalendar có mã thông báo này hiện không hợp lệ.' password_confirmation_dialog: - confirmation_required: "You need to enter your account password to confirm this change." - title: "Confirm your password to continue" + confirmation_required: "Bạn cần nhập mật khẩu tài khoản của mình để xác nhận thay đổi này." + title: "Xác nhận mật khẩu của bạn để tiếp tục" news: index: no_results_title_text: Hiện không có tin tức báo cáo. no_results_content_text: Thêm một mục tin tức roles: permissions: - section_check_all_label: "Assign all %{module} permissions" - section_uncheck_all_label: "Unassign all %{module} permissions" + section_check_all_label: "Gán tất cả các quyền %{module}" + section_uncheck_all_label: "Bỏ gán tất cả quyền %{module}" report: - matrix_caption: "Permissions matrix for %{module} module" - matrix_checkbox_label: "Assign %{permission} permission to %{role} role" - matrix_check_all_label: "Assign all %{module} permissions to all roles" - matrix_uncheck_all_label: "Unassign all %{module} permissions from all roles" - matrix_check_uncheck_all_in_row_label_html: "Toggle %{permission} permission for all roles" - matrix_check_uncheck_all_in_col_label_html: "Toggle all %{module} permissions for %{role} role" + matrix_caption: "Ma trận quyền cho mô-đun %{module}" + matrix_checkbox_label: "Gán quyền %{permission} cho vai trò %{role}" + matrix_check_all_label: "Gán tất cả các quyền %{module} cho tất cả các vai trò" + matrix_uncheck_all_label: "Bỏ gán tất cả các quyền %{module} khỏi tất cả các vai trò" + matrix_check_uncheck_all_in_row_label_html: "Chuyển đổi quyền %{permission} cho tất cả các vai trò" + matrix_check_uncheck_all_in_col_label_html: "Bật/tắt tất cả quyền truy cập của %{module} cho vai trò %{role}" users: autologins: - prompt: "Giữ đăng nhập trong %{num_days}" + prompt: "Duy trì đăng nhập cho %{num_days}" sessions: session_name: "%{browser_name} %{browser_version} trên %{os_name}" - browser: "Trình duyệt" + browser: "trình duyệt" expires: "Hết hạn" - last_connection: "Kết nối lần cuối" - device: "Thiết bị / Hệ điều hành" + last_connection: "Kết nối cuối cùng" + device: "Thiết bị/HĐH" unknown_browser: "trình duyệt không xác định" unknown_os: "hệ điều hành không xác định" - unknown: "(unknown)" - browser_session: "(Browser session)" - current: "Current (this device)" + unknown: "(không rõ)" + browser_session: "(Phiên trình duyệt)" + current: "Hiện tại (thiết bị này)" title: "Quản lý phiên" - instructions: "You are logged in to your account through the following devices. Revoke sessions that you do not recognise or from devices you do not control." + instructions: "Bạn đã đăng nhập vào tài khoản của mình thông qua các thiết bị sau. Thu hồi các phiên mà bạn không nhận ra hoặc từ các thiết bị bạn không kiểm soát." may_not_delete_current: "Bạn không thể xóa phiên hiện tại của mình." - deletion_warning: "Are you sure you want to revoke this session? You will be logged out on this device." + deletion_warning: "Bạn có chắc chắn muốn thu hồi phiên này không? Bạn sẽ đăng xuất trên thiết bị này." groups: member_in_these_groups: "Người dùng này hiện là thành viên của các nhóm sau:" - no_results_title_text: Người dùng này hiện không phải là thành viên của nhóm nào. - summary_with_more: Member of %{names} and %{count_link}. - more: "%{count} more" - summary: Member of %{names}. + no_results_title_text: Người dùng này hiện không phải là thành viên trong bất kỳ nhóm nào. + summary_with_more: Thành viên của %{names} và %{count_link}. + more: "%{count} thêm" + summary: Thành viên của %{names}. memberships: no_results_title_text: Người dùng này không phải là thành viên của dự án. - open_profile: "Open profile" + open_profile: "Mở hồ sơ" invite_user_modal: invite: "Mời" title: invite: "Mời người dùng" - invite_to_project: "Mời %{type} vào %{project}" - invite_principal_to_project: "Mời %{principal} vào %{project}" + invite_to_project: "Mời %{type} tới %{project}" + invite_principal_to_project: "Mời %{principal} tới %{project}" project: - label: "Dự án" + label: "dự án" required: "Vui lòng chọn một dự án" - next_button: "Tiếp" - no_results: "Không tìm thấy dự án nào" + next_button: "Tiếp theo" + no_results: "Không có dự án nào được tìm thấy" no_invite_rights: "Bạn không được phép mời thành viên vào dự án này" type: - required: "Vui lòng chọn loại để mời" + required: "Vui lòng chọn loại được mời" user: - title: "Invite user to %{project_name}" - description: "Quyền dựa trên vai trò được phân công trong dự án đã chọn" + title: "Mời người dùng %{project_name}" + description: "Quyền dựa trên vai trò được giao trong dự án đã chọn" group: - title: "Invite group to %{project_name}" - description: "Quyền dựa trên vai trò được phân công trong dự án đã chọn" + title: "Mời nhóm tham gia %{project_name}" + description: "Quyền dựa trên vai trò được giao trong dự án đã chọn" placeholder_user: - title: "Add placeholder user to %{project_name}" - title_no_ee: "Người dùng tạm thời (chỉ có trong phiên bản Doanh nghiệp)" - description: "Không có quyền truy cập vào dự án và không có email được gửi đi." + title: "Thêm người dùng giữ chỗ vào %{project_name}" + title_no_ee: "Người dùng giữ chỗ (tiện ích bổ sung chỉ dành cho phiên bản Enterprise)" + description: "Không có quyền truy cập vào dự án và không có email nào được gửi đi." already_member_message: "Đã là thành viên của %{project}" principal: - no_results_user: "Không tìm thấy người dùng" + no_results_user: "Không tìm thấy người dùng nào" invite_user: "Mời:" - no_results_placeholder: "Không tìm thấy người dùng tạm thời" - create_new_placeholder: "Tạo người dùng tạm thời mới:" - no_results_group: "Không tìm thấy nhóm" - invite_to_project: "Invite to %{project_name}" + no_results_placeholder: "Không tìm thấy phần giữ chỗ nào" + create_new_placeholder: "Tạo trình giữ chỗ mới:" + no_results_group: "Không tìm thấy nhóm nào" + invite_to_project: "Mời tham gia %{project_name}" required: user: "Vui lòng chọn một người dùng" - placeholder: "Vui lòng chọn một người dùng tạm thời" + placeholder: "Vui lòng chọn một trình giữ chỗ" group: "Vui lòng chọn một nhóm" role: label: "Vai trò trong %{project}" no_roles_found: "Không tìm thấy vai trò nào" description: > - This is the role that the user will receive when they join your project. The role defines which actions they are allowed to take and which information they are allowed to see. [Learn more about roles and permissions.](docs_url) + Đây là vai trò mà người dùng sẽ nhận được khi họ tham gia dự án của bạn. Vai trò xác định những hành động nào họ được phép thực hiện và thông tin nào họ được phép xem. [Learn more about roles and permissions.](docs_url) required: "Vui lòng chọn một vai trò" message: label: "Tin nhắn mời" - description: "We will send an email to the user, to which you can add a personal message here. An explanation for the invitation could be useful, or perhaps a bit of information regarding the project to help them get started." + description: "Chúng tôi sẽ gửi email cho người dùng để bạn có thể thêm tin nhắn cá nhân tại đây. Lời giải thích cho lời mời có thể hữu ích hoặc có thể là một chút thông tin liên quan đến dự án để giúp họ bắt đầu." summary: next_button: "Gửi lời mời" success_message: - user: "Người dùng có thể đăng nhập để truy cập %{project}. Trong khi đó, bạn có thể lên kế hoạch với người dùng đó và phân công các gói công việc ví dụ." - placeholder_user: "Người dùng tạm thời có thể được sử dụng trong %{project}. Trong khi đó, bạn có thể lên kế hoạch với người dùng đó và phân công các gói công việc ví dụ." - group: "Nhóm hiện đã là một phần của %{project}. Trong khi đó, bạn có thể lên kế hoạch với nhóm đó và phân công các gói công việc ví dụ." + user: "Bây giờ người dùng có thể đăng nhập để truy cập %{project}. Trong khi đó, bạn có thể lập kế hoạch với người dùng đó và chỉ định các gói công việc chẳng hạn." + placeholder_user: "Trình giữ chỗ hiện có thể được sử dụng trong %{project}. Trong khi đó, bạn có thể lập kế hoạch với người dùng đó và chỉ định các gói công việc chẳng hạn." + group: "Nhóm hiện là một phần của %{project}. Trong khi đó, bạn có thể lập kế hoạch với nhóm đó và phân công các gói công việc chẳng hạn." page: - text: "Văn bản" + text: "văn bản" placeholder_users: right_to_manage_members_missing: > - Bạn không được phép xóa người dùng giả định. Bạn không có quyền quản lý thành viên cho tất cả các dự án mà người dùng giả định là thành viên. - delete_tooltip: "Xóa người dùng giả định" + Bạn không được phép xóa người dùng giữ chỗ. Bạn không có quyền quản lý thành viên cho tất cả các dự án mà người dùng giữ chỗ là thành viên. + delete_tooltip: "Xóa người dùng giữ chỗ" deletion_info: - heading: "Xóa người dùng giả định %{name}" + heading: "Xóa người dùng giữ chỗ %{name}" data_consequences: > - Tất cả các trường hợp của người dùng giả định (ví dụ, làm người giao việc, người chịu trách nhiệm hoặc các giá trị người dùng khác) sẽ được chuyển nhượng cho một tài khoản có tên là "Người dùng đã xóa". Vì dữ liệu của mỗi tài khoản bị xóa được chuyển nhượng cho tài khoản này, nên sẽ không thể phân biệt dữ liệu mà người dùng đã tạo từ dữ liệu của một tài khoản đã xóa khác. - irreversible: "Hành động này không thể đảo ngược" - confirmation: "Nhập tên người dùng giả định %{name} để xác nhận việc xóa." + Tất cả các lần xuất hiện của người dùng giữ chỗ (ví dụ: với tư cách là người được chuyển nhượng, người chịu trách nhiệm hoặc giá trị người dùng khác) sẽ được chỉ định lại cho tài khoản có tên "Người dùng đã xóa". Vì dữ liệu của mọi tài khoản đã xóa được gán lại cho tài khoản này nên sẽ không thể phân biệt dữ liệu người dùng đã tạo với dữ liệu của tài khoản đã xóa khác. + irreversible: "Hành động này không thể thay đổi được" + confirmation: "Nhập tên người dùng giữ chỗ %{name} để xác nhận việc xóa." priorities: edit: priority_color_text: | - Nhấp để chỉ định hoặc thay đổi màu của mức ưu tiên này. - Có thể sử dụng để làm nổi bật các gói công việc trong bảng. + Nhấn vào đây để gán hoặc thay đổi màu sắc của mức độ ưu tiên này. + Nó có thể được sử dụng để làm nổi bật các gói công việc trong bảng. admin: default: - caption: Making this priority default will override the previous default priority. + caption: Đặt mức độ ưu tiên này làm mặc định sẽ ghi đè mức độ ưu tiên mặc định trước đó. reactions: - action_title: "React" - add_reaction: "Add reaction" - react_with: "React with %{reaction}" - and_user: "and %{user}" + action_title: "phản ứng" + add_reaction: "Thêm phản ứng" + react_with: "Phản ứng với %{reaction}" + and_user: "và %{user}" and_others: - other: và %{count} nữa - reaction_by: "%{reaction} by" + other: và %{count} người khác + reaction_by: "%{reaction} bởi" reportings: index: no_results_title_text: Hiện tại không có báo cáo trạng thái. - no_results_content_text: Thêm một báo cáo trạng thái + no_results_content_text: Thêm báo cáo trạng thái statuses: edit: status_color_text: | - Nhấp để gán hoặc thay đổi màu của trạng thái này. - Nó được hiển thị trên nút trạng thái và có thể được sử dụng để làm nổi bật các gói công việc trong bảng. + Nhấn vào đây để gán hoặc thay đổi màu sắc của trạng thái này. + Nó được hiển thị trong nút trạng thái và có thể được sử dụng để đánh dấu các gói công việc trong bảng. status_default_text: |- - New work packages are by default set to this type. They cannot be read-only. + Các gói công việc mới theo mặc định được đặt thành loại này. Chúng không thể ở chế độ chỉ đọc. status_excluded_from_totals_text: |- - Đánh dấu tùy chọn này để loại trừ các gói công việc có trạng thái này khỏi tổng số Công việc, - Công việc còn lại, và % Hoàn thành trong một cấu trúc phân cấp. + Chọn tùy chọn này để loại trừ các gói công việc có trạng thái này khỏi tổng số Công việc, + Công việc còn lại và % Hoàn thành theo thứ bậc. status_percent_complete_text: |- - In status-based progress calculation mode, the % Complete of a work - package is automatically set to this value when this status is selected. - Ignored in work-based mode. + Trong chế độ tính toán tiến độ dựa trên trạng thái, phần trăm hoàn thành của gói công việc sẽ được tự động đặt thành giá trị này khi trạng thái này được chọn. Tỷ lệ hoàn thành dựa trên công việc bị bỏ qua trong chế độ dựa trên công việc. status_readonly_html: | - Check this option to mark work packages with this status as read-only. - No attributes can be changed with the exception of the status. + Chọn tùy chọn này để đánh dấu các gói công việc có trạng thái này là chỉ đọc. + Không có thuộc tính nào có thể được thay đổi ngoại trừ trạng thái.
    - Note: Inherited values (e.g., from children or relations) will still apply. + Lưu ý: Các giá trị được kế thừa (ví dụ: từ con cái hoặc quan hệ) sẽ vẫn được áp dụng. index: - no_results_title_text: Hiện tại không có trạng thái gói công việc nào. + no_results_title_text: Hiện tại không có trạng thái gói công việc. no_results_content_text: Thêm trạng thái mới headers: - is_default: "Mặc định" - is_closed: "Đã đóng" + is_default: "mặc định" + is_closed: "đóng cửa" is_readonly: "Chỉ đọc" - excluded_from_totals: "Loại trừ khỏi tổng số" + excluded_from_totals: "Bị loại trừ khỏi tổng số" themes: - dark: "Dark" - light: "Sáng" - sync_with_os: "Automatic (match OS color mode)" + dark: "Tối" + light: "ánh sáng" + sync_with_os: "Tự động (khớp với chế độ màu của hệ điều hành)" types: index: no_results_title_text: Hiện tại không có loại nào. no_results_content_text: Tạo loại mới edit: form_configuration: - tab: "Mẫu cấu hình" + tab: "Cấu hình biểu mẫu" projects: - tab: Các dự án - enable_all: Enable for all projects - select_projects: Select projects - select_projects_description: Select the projects in which you would like to use this type. + tab: dự án + enable_all: Kích hoạt cho tất cả các dự án + select_projects: Chọn dự án + select_projects_description: Chọn các dự án mà bạn muốn sử dụng loại này. settings: - tab: "Cài đặt" - type_color_text: The selected color distinguishes different types in Gantt charts or work packages tables. It is therefore recommended to use a strong color. + tab: "cài đặt" + type_color_text: Màu được chọn sẽ phân biệt các loại khác nhau trong biểu đồ Gantt hoặc bảng gói công việc. Do đó, nên sử dụng màu sắc mạnh mẽ. subject_configuration: - tab: "Subject configuration" + tab: "Cấu hình chủ đề" manually_editable_subjects: - label: "Manually editable subjects" - caption: "Users can manually enter and edit work package subjects without restrictions." + label: "Các chủ đề có thể chỉnh sửa thủ công" + caption: "Người dùng có thể nhập và chỉnh sửa chủ đề gói công việc theo cách thủ công mà không bị hạn chế." automatically_generated_subjects: - label: "Automatically generated subjects" - caption: "Define a pattern using referenced attributes and text to automatically generate work package subjects. Users will not be able to manually edit subjects." + label: "Chủ đề được tạo tự động" + caption: "Xác định mẫu bằng cách sử dụng các thuộc tính và văn bản được tham chiếu để tự động tạo chủ đề gói công việc. Người dùng sẽ không thể chỉnh sửa chủ đề theo cách thủ công." token: label_with_context: "%{attribute_context}: %{attribute_label}" context: - work_package: "Work Package" - parent: "Cha" - project: "Dự án" + work_package: "Gói công việc" + parent: "cha mẹ" + project: "dự án" pattern: - label: "Subject pattern" - caption: Create patterns by adding text, or type "/" to search for [supported attributes](attributes_url). - insert_as_text: 'No attributes found. Add as text: "%{word}"' + label: "mẫu chủ đề" + caption: Tạo mẫu bằng cách thêm văn bản hoặc nhập "/" để tìm kiếm [supported attributes](attributes_url). + insert_as_text: 'Không tìm thấy thuộc tính nào. Thêm dưới dạng văn bản: "%{word}"' export_configuration: - tab: "Generate PDF" - intro: "Select which templates from those that are available you would like to enable for this type. The template determines the design and attributes visible in the exported PDF of a work package using this type. The first template on the list is selected by default." + tab: "Tạo PDF" + intro: "Chọn mẫu nào trong số những mẫu có sẵn mà bạn muốn bật cho loại này. Mẫu xác định thiết kế và các thuộc tính hiển thị trong tệp PDF đã xuất của gói công việc sử dụng loại này. Mẫu đầu tiên trong danh sách được chọn theo mặc định." pdf_export_templates: - label: "PDF Export templates" + label: "Mẫu xuất PDF" actions: - label_enable_all: "Bật tất cả" - label_disable_all: "Tắt tất cả" + label_enable_all: "Kích hoạt tất cả" + label_disable_all: "Vô hiệu hóa tất cả" versions: overview: - work_packages_in_archived_projects: "Phiên bản này được chia sẻ với các dự án đã lưu trữ vẫn có gói công việc được gán cho phiên bản này. Những gói công việc này được tính, nhưng sẽ không xuất hiện trong các chế độ xem liên kết." + work_packages_in_archived_projects: "Phiên bản này được chia sẻ với các dự án đã lưu trữ vẫn có các gói công việc được gán cho phiên bản này. Những nội dung này được tính nhưng sẽ không xuất hiện trong các chế độ xem được liên kết." no_results_title_text: Hiện tại không có gói công việc nào được gán cho phiên bản này. wiki: - page_not_editable_index: Trang yêu cầu không (chưa) tồn tại. Bạn đã được chuyển hướng đến chỉ mục của tất cả các trang wiki. + page_not_editable_index: Trang được yêu cầu không (chưa) tồn tại. Bạn đã được chuyển hướng đến chỉ mục của tất cả các trang wiki. no_results_title_text: Hiện tại không có trang wiki nào. - print_hint: Điều này sẽ in nội dung của trang wiki này mà không có bất kỳ thanh điều hướng nào. + print_hint: Thao tác này sẽ in nội dung của trang wiki này mà không có bất kỳ thanh điều hướng nào. index: no_results_content_text: Thêm một trang wiki mới workflows: form: - matrix_caption: "Workflow matrix" - matrix_caption_assignee: "Workflow matrix for assignee" - matrix_caption_author: "Workflow matrix for author" - matrix_checkbox_label: "Allow transition from %{old_status} to %{new_status}" - matrix_check_all_label: "Allow all transitions" - matrix_uncheck_all_label: "Disallow all transitions" - matrix_check_uncheck_all_in_row_label_html: "Toggle transitions from %{old_status} to all new statuses" - matrix_check_uncheck_all_in_col_label_html: "Toggle transitions from all old statuses to %{new_status}" + matrix_caption: "Ma trận quy trình công việc" + matrix_caption_assignee: "Ma trận quy trình công việc cho người được giao" + matrix_caption_author: "Ma trận quy trình làm việc cho tác giả" + matrix_checkbox_label: "Cho phép chuyển đổi từ %{old_status} sang %{new_status}" + matrix_check_all_label: "Cho phép tất cả các chuyển tiếp" + matrix_uncheck_all_label: "Không cho phép tất cả các chuyển đổi" + matrix_check_uncheck_all_in_row_label_html: "Chuyển đổi các chuyển đổi từ %{old_status} sang tất cả các trạng thái mới" + matrix_check_uncheck_all_in_col_label_html: "Chuyển đổi trạng thái từ tất cả các trạng thái cũ sang %{new_status}" work_flows: index: no_results_title_text: Hiện tại không có quy trình làm việc nào. @@ -964,181 +962,181 @@ vi: datepicker_modal: banner: description: - automatic_mobile: "Start date derived." - click_on_show_relations_to_open_gantt: 'Click on "%{button_name}" for Gantt overview.' - manual_mobile: "Ignoring relations." - manual_gap_between_predecessors: "There is a gap between this and all predecessors." - manual_overlap_with_predecessors: "Overlaps with at least one predecessor." - manual_with_children: "This has child work packages but their start dates are ignored." + automatic_mobile: "Ngày bắt đầu bắt nguồn." + click_on_show_relations_to_open_gantt: 'Nhấp vào "%{button_name}" để xem tổng quan về Gantt.' + manual_mobile: "Bỏ qua các mối quan hệ." + manual_gap_between_predecessors: "Có một khoảng cách giữa điều này và tất cả những người đi trước." + manual_overlap_with_predecessors: "Trùng lặp với ít nhất một người tiền nhiệm." + manual_with_children: "Điều này có các gói công việc con nhưng ngày bắt đầu của chúng bị bỏ qua." title: - automatic_mobile: "Automatically scheduled." - automatic_with_children: "The dates are determined by child work packages." - automatic_with_predecessor: "The start date is set by a predecessor." - manual_mobile: "Manually scheduled." - manually_scheduled: "Manually scheduled. Dates not affected by relations." + automatic_mobile: "Tự động lên lịch." + automatic_with_children: "Ngày được xác định bởi các gói công việc trẻ em." + automatic_with_predecessor: "Ngày bắt đầu do người tiền nhiệm ấn định." + manual_mobile: "Lên lịch thủ công." + manually_scheduled: "Lên lịch thủ công. Ngày không bị ảnh hưởng bởi các mối quan hệ." blankslate: - title: "No predecessors" - description: "To enable automatic scheduling, this work package needs to have at least one predecessor. It will then automatically be scheduled to start after the closest predecessor." + title: "Không có người tiền nhiệm" + description: "Để kích hoạt tính năng lập lịch trình tự động, gói công việc này cần phải có ít nhất một gói công việc trước đó. Sau đó, nó sẽ tự động được lên lịch để bắt đầu sau phiên bản trước gần nhất." ignore_non_working_days: title: "Chỉ ngày làm việc" mode: - title: "Scheduling mode" - automatic: "Tự động" - manual: "Manual" - show_relations: "Hiển thị các mối quan hệ" - update_inputs_aria_live_message: "Date picker updated. %{message}" + title: "Chế độ lập lịch" + automatic: "tự động" + manual: "hướng dẫn sử dụng" + show_relations: "Hiển thị mối quan hệ" + update_inputs_aria_live_message: "Đã cập nhật bộ chọn ngày. %{message}" tabs: - aria_label: "Datepicker tabs" - children: "Con" - dates: "Dates" - predecessors: "Predecessors" - successors: "Successors" + aria_label: "Tab chọn ngày" + children: "bọn trẻ" + dates: "ngày tháng" + predecessors: "Người tiền nhiệm" + successors: "người kế nhiệm" blankslate: predecessors: - title: "No predecessors" - description: "This work package does not have any predecessors." + title: "Không có người tiền nhiệm" + description: "Gói công việc này không có gói công việc nào trước đó." successors: - title: "No successors" - description: "This work package does not have any successors." + title: "Không có người kế nhiệm" + description: "Gói công việc này không có phần kế thừa nào." children: - title: "No children" - description: "This work package does not have any children." + title: "Không có con" + description: "Gói công việc này không có con." x_descendants: other: "%{count} gói sản phẩm phụ thuộc" bulk: copy_failed: "Các gói công việc không thể được sao chép." move_failed: "Các gói công việc không thể được di chuyển." could_not_be_saved: "Không thể lưu các gói công việc sau:" - none_could_be_saved: "Không có gói công việc nào trong số %{total} có thể được cập nhật." - x_out_of_y_could_be_saved: "%{failing} trong số %{total} gói công việc không thể được cập nhật trong khi %{success} thì có thể." - selected_because_descendants: "While %{selected} work packages were selected, in total %{total} work packages are affected which includes descendants." - descendant: "gói công việc kế thừa của được chọn" + none_could_be_saved: "Không có gói công việc %{total} nào có thể được cập nhật." + x_out_of_y_could_be_saved: "Không thể cập nhật %{failing} trong số %{total} gói công việc trong khi %{success} thì có thể." + selected_because_descendants: "Mặc dù các gói công việc %{selected} đã được chọn nhưng tổng số các gói công việc %{total} đều bị ảnh hưởng, bao gồm cả các gói công việc con." + descendant: "hậu duệ của người được chọn" move: no_common_statuses_exists: "Có là tình trạng không có sẵn cho tất cả các gói đã chọn công việc. Tình trạng của họ không thể thay đổi." - unsupported_for_multiple_projects: "Di chuyển/ sao chép hàng loạt không được hỗ trợ cho các gói công việc từ nhiều dự án" + unsupported_for_multiple_projects: "Di chuyển/sao chép hàng loạt không được hỗ trợ cho các gói công việc từ nhiều dự án" current_type_not_available_in_target_project: > - Loại hiện tại của gói công việc không được kích hoạt trong dự án mục tiêu. Vui lòng kích hoạt loại trong dự án mục tiêu nếu bạn muốn chúng không thay đổi. Nếu không, loại của gói công việc sẽ được tự động gán lại dẫn đến mất dữ liệu tiềm ẩn. + Loại gói công việc hiện tại không được kích hoạt trong dự án mục tiêu. Vui lòng bật loại trong dự án mục tiêu nếu bạn muốn nó không thay đổi. Nếu không, hãy chọn loại có sẵn trong dự án mục tiêu từ danh sách. bulk_current_type_not_available_in_target_project: > - Các loại hiện tại của các gói công việc không được kích hoạt trong dự án mục tiêu. Vui lòng kích hoạt các loại trong dự án mục tiêu nếu bạn muốn chúng không thay đổi. Nếu không, các loại của gói công việc sẽ được tự động gán lại dẫn đến mất dữ liệu tiềm ẩn. + Các loại gói công việc hiện tại không được kích hoạt trong dự án mục tiêu. Vui lòng kích hoạt các loại trong dự án mục tiêu nếu bạn muốn chúng không thay đổi. Nếu không, hãy chọn loại có sẵn trong dự án mục tiêu từ danh sách. sharing: missing_workflow_warning: - title: "Thiếu quy trình làm việc cho việc chia sẻ gói công việc" - message: "Không có quy trình làm việc nào được cấu hình cho vai trò 'Biên tập viên gói công việc'. Không có quy trình làm việc, người được chia sẻ không thể thay đổi trạng thái của gói công việc. Các quy trình làm việc có thể được sao chép. Chọn một loại nguồn (ví dụ: 'Nhiệm vụ') và vai trò nguồn (ví dụ: 'Thành viên'). Sau đó chọn các loại mục tiêu. Để bắt đầu, bạn có thể chọn tất cả các loại làm mục tiêu. Cuối cùng, chọn vai trò 'Biên tập viên gói công việc' làm mục tiêu và nhấp vào 'Sao chép'. Sau khi đã tạo ra các mặc định như vậy, tinh chỉnh các quy trình làm việc như bạn làm với mọi vai trò khác." - link_message: "Cấu hình các quy trình làm việc trong quản trị." - templated_subject_hint: Automatically generated through type %{type} + title: "Thiếu luồng công việc để chia sẻ gói công việc" + message: "Không có quy trình làm việc nào được định cấu hình cho vai trò 'Trình chỉnh sửa gói công việc'. Nếu không có quy trình làm việc, nội dung được chia sẻ với người dùng không thể thay đổi trạng thái của gói công việc. Quy trình làm việc có thể được sao chép. Chọn loại nguồn (ví dụ: 'Nhiệm vụ') và vai trò nguồn (ví dụ: 'Thành viên'). Sau đó chọn loại mục tiêu. Để bắt đầu, bạn có thể chọn tất cả các loại làm mục tiêu. Cuối cùng, chọn vai trò 'Trình chỉnh sửa gói công việc' làm mục tiêu và nhấn 'Sao chép'. Sau khi đã tạo các giá trị mặc định, hãy tinh chỉnh quy trình làm việc như bạn thực hiện với mọi vai trò khác." + link_message: "Cấu hình các quy trình công việc trong quản trị." + templated_subject_hint: Được tạo tự động thông qua loại %{type} summary: reports: category: no_results_title_text: Hiện tại không có danh mục nào có sẵn. assigned_to: - no_results_title_text: Hiện có không có thành viên của dự án này. + no_results_title_text: Hiện tại không có thành viên nào tham gia dự án này. responsible: - no_results_title_text: Hiện có không có thành viên của dự án này. + no_results_title_text: Hiện tại không có thành viên nào tham gia dự án này. author: - no_results_title_text: Hiện có không có thành viên của dự án này. + no_results_title_text: Hiện tại không có thành viên nào tham gia dự án này. priority: - no_results_title_text: Hiện tại không có mức ưu tiên nào có sẵn. + no_results_title_text: Hiện tại không có ưu tiên nào. type: no_results_title_text: Không có phân loại. version: no_results_title_text: Hiện tại không sẵn có phiên bản nào. 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_child_button: "Child" - label_add_x: "Add %{x}" - label_edit_x: "Edit %{x}" - label_add_description: "Add description" + action_bar_title: "Thêm quan hệ vào các gói công việc khác để tạo liên kết giữa chúng." + no_results_title_text: Hiện tại chưa có mối quan hệ nào. + blankslate_heading: "Không có quan hệ" + blankslate_description: "Gói công việc này chưa có bất kỳ mối quan hệ nào." + label_add_child_button: "đứa trẻ" + label_add_x: "Thêm %{x}" + label_edit_x: "Chỉnh sửa %{x}" + label_add_description: "Thêm mô tả" lag: - subject: "Kết quả chậm" + subject: "Độ trễ" caption: |- - The minimum number of working days to keep in between the two work packages. - It can also be negative. + Số ngày làm việc tối thiểu cần giữ giữa hai gói công việc. + Nó cũng có thể là tiêu cực. relations: - label_new_child_created: "New work package created and added as a child" + label_new_child_created: "Gói công việc mới được tạo và thêm khi còn nhỏ" label_relates_singular: "liên quan đến" label_relates_plural: "liên quan đến" label_relates_to_singular: "liên quan đến" label_relates_to_plural: "liên quan đến" - 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" + relates_description: "Tạo liên kết hiển thị giữa hai gói công việc mà không có tác dụng bổ sung" + relates_to_description: "Tạo liên kết hiển thị giữa hai gói công việc mà không có tác dụng bổ sung" + label_precedes_singular: "người kế vị (sau)" + label_precedes_plural: "người kế vị (sau)" + precedes_description: "Gói công việc liên quan nhất thiết phải bắt đầu sau khi gói công việc này kết thúc" + label_follows_singular: "người tiền nhiệm (trước đây)" + label_follows_plural: "người tiền nhiệm (trước đây)" + follows_description: "Gói công việc liên quan nhất thiết phải hoàn thành trước khi gói công việc này có thể bắt đầu" + label_child_singular: "đứa trẻ" + label_child_plural: "bọn trẻ" new_child: "Tạo con mới" - new_child_description: "Creates a related work package as a sub-item of the current (parent) work package" - child: "Child" - child_description: "Makes the related work package a sub-item of the current (parent) work package" - parent: "Cha" - parent_description: "Makes the related work package a parent of the current (child) work package" - label_closest: "Closest" - label_blocks_singular: "các khối" - label_blocks_plural: "các khối" - blocks_description: "The related work package cannot be closed until this one is closed first" + new_child_description: "Tạo gói công việc liên quan dưới dạng mục con của gói công việc (cha) hiện tại" + child: "đứa trẻ" + child_description: "Làm cho gói công việc liên quan trở thành một mục con của gói công việc (mẹ) hiện tại" + parent: "cha mẹ" + parent_description: "Biến gói công việc liên quan thành cha của gói công việc (con) hiện tại" + label_closest: "Gần nhất" + label_blocks_singular: "khối" + label_blocks_plural: "khối" + blocks_description: "Không thể đóng gói công việc liên quan cho đến khi gói công việc này được đóng trước" label_blocked_singular: "bị chặn bởi" label_blocked_plural: "bị chặn bởi" label_blocked_by_singular: "bị chặn bởi" label_blocked__by_plural: "bị chặn bởi" - 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: "Nhân đôi" - label_duplicates_plural: "Nhân đôi" - duplicates_description: "This is a copy of the related work package" - label_duplicated_singular: "bị trùng bởi" - label_duplicated_plural: "bị trùng bởi" - label_duplicated_by_singular: "bị trùng bởi" - label_duplicated_by_plural: "bị trùng bởi" - duplicated_by_description: "The related work package is a copy of this" - duplicated_description: "The related work package is a copy of this" + blocked_description: "Gói công việc này không thể được đóng cho đến khi gói công việc liên quan được đóng trước" + blocked_by_description: "Gói công việc này không thể được đóng cho đến khi gói công việc liên quan được đóng trước" + label_duplicates_singular: "trùng lặp" + label_duplicates_plural: "trùng lặp" + duplicates_description: "Đây là bản sao của gói công việc liên quan" + label_duplicated_singular: "nhân đôi bởi" + label_duplicated_plural: "nhân đôi bởi" + label_duplicated_by_singular: "nhân đôi bởi" + label_duplicated_by_plural: "nhân đôi bởi" + duplicated_by_description: "Gói công việc liên quan là bản sao của gói công việc này" + duplicated_description: "Gói công việc liên quan là bản sao của gói công việc này" label_includes_singular: "bao gồm" label_includes_plural: "bao gồm" - includes_description: "Marks the related work package as including this one with no additional effect" + includes_description: "Đánh dấu gói công việc liên quan là bao gồm gói này mà không có tác dụng bổ sung" label_partof_singular: "một phần của" label_partof_plural: "một phần của" label_part_of_singular: "một phần của" label_part_of_plural: "một phần của" - 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: "bắt buộc" - label_requires_plural: "bắt buộc" - 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_other_relations: "Other relations" - ghost_relation_title: "Work package liên quan" - ghost_relation_description: "This is not visible to you due to permissions." + partof_description: "Đánh dấu gói công việc liên quan là một phần của gói này mà không có tác dụng bổ sung" + part_of_description: "Đánh dấu gói công việc liên quan là một phần của gói này mà không có tác dụng bổ sung" + label_requires_singular: "Yêu cầu" + label_requires_plural: "Yêu cầu" + requires_description: "Đánh dấu gói công việc liên quan là yêu cầu đối với gói công việc này" + label_required_singular: "được yêu cầu bởi" + label_required_plural: "được yêu cầu bởi" + required_description: "Đánh dấu gói công việc này là một yêu cầu đối với gói công việc liên quan" + label_parent_singular: "cha mẹ" + label_parent_plural: "cha mẹ" + label_other_relations: "Các mối quan hệ khác" + ghost_relation_title: "Gói công việc liên quan" + ghost_relation_description: "Điều này không hiển thị với bạn do quyền." label_invitation: Thư mời account: delete: "Xoá tài khoản" delete_confirmation: "Bạn có chắc chắn muốn xoá tài khoản chứ?" - deletion_pending: "Account has been scheduled for deletion. Note that this process takes place in the background. It might take a few moments until the user is fully deleted." + deletion_pending: "Tài khoản đã được lên lịch xóa. Lưu ý rằng quá trình này diễn ra ở chế độ nền. Có thể mất vài phút cho đến khi người dùng bị xóa hoàn toàn." deletion_info: data_consequences: - other: "All user-specific data will be deleted. The user's activity in shared views such as work packages and meetings will not be deleted but instead be associated with a generic 'Deleted user' that cannot be linked to the original account." - self: "All user-specific data will be deleted. Your activity in shared views such as work packages and meetings will not be deleted but instead be associated with a generic 'Deleted user' that cannot be linked to your original account." - heading: "Delete %{name}'s account?" + other: "Tất cả dữ liệu cụ thể của người dùng sẽ bị xóa. Hoạt động của người dùng trong các chế độ xem được chia sẻ như gói công việc và cuộc họp sẽ không bị xóa mà thay vào đó sẽ được liên kết với một 'Người dùng đã xóa' chung không thể liên kết với tài khoản ban đầu." + self: "Tất cả dữ liệu cụ thể của người dùng sẽ bị xóa. Hoạt động của bạn trong các chế độ xem được chia sẻ như gói công việc và cuộc họp sẽ không bị xóa mà thay vào đó sẽ được liên kết với một 'Người dùng đã xóa' chung không thể liên kết với tài khoản ban đầu của bạn." + heading: "Xóa tài khoản của %{name}?" login_consequences: - other: "This account will immediately be removed from the system and the user will no longer be able to log in with their credentials." - self: "Your account will immediately be removed from the system and you will no longer be able to log in using your credentials." + other: "Tài khoản này sẽ ngay lập tức bị xóa khỏi hệ thống và người dùng sẽ không thể đăng nhập bằng thông tin đăng nhập của mình nữa." + self: "Tài khoản của bạn sẽ ngay lập tức bị xóa khỏi hệ thống và bạn sẽ không thể đăng nhập bằng thông tin đăng nhập của mình nữa." error_inactive_activation_by_mail: > - Tài khoản của bạn đã không được nêu ra được kích hoạt. Để kích hoạt tài khoản của bạn, nhấp vào liên kết được gửi đến bạn. + Tài khoản của bạn vẫn chưa được kích hoạt. Để kích hoạt tài khoản của bạn, hãy nhấp vào liên kết được gửi qua email cho bạn. error_inactive_manual_activation: > Tài khoản của bạn đã không được nêu ra được kích hoạt. Xin vui lòng chờ cho người quản trị để kích hoạt tài khoản của bạn. error_self_registration_disabled: > Đăng ký người dùng bị vô hiệu hóa trên hệ thống này. Xin vui lòng hỏi người quản trị để tạo ra một tài khoản cho bạn. error_self_registration_limited_provider: > - Đăng ký người dùng bị hạn chế cho nhà cung cấp Đăng nhập một lần '%{name}'. Vui lòng yêu cầu quản trị viên kích hoạt tài khoản của bạn hoặc thay đổi giới hạn đăng ký tự động cho nhà cung cấp này. + Đăng ký người dùng bị giới hạn đối với nhà cung cấp dịch vụ đăng nhập một lần '%{name}'. Vui lòng yêu cầu quản trị viên kích hoạt tài khoản cho bạn hoặc thay đổi giới hạn tự đăng ký cho nhà cung cấp này. login_with_auth_provider: "hoặc đăng nhập bằng tài khoản hiện tại" signup_with_auth_provider: "hoặc sử dụng đăng ký" auth_source_login: Vui lòng đăng nhập với %{login} để kích hoạt tài khoản của bạn. @@ -1147,7 +1145,7 @@ vi: activemodel: attributes: projects/copy_options: - dependencies: "Phụ thuộc" + dependencies: "phụ thuộc" activerecord: attributes: announcements: @@ -1155,42 +1153,42 @@ vi: attachment: attachment_content: "Số lượng Tệp đính kèm" attachment_file_name: "Tên tập tin đính kèm" - content_type: "Content-type" + content_type: "Loại nội dung" downloads: "Những mục đã tải xuống" - file: "Tệp" + file: "tập tin" filename: "Tệp" filesize: "Kích cỡ" attribute_help_text: attribute_name: "Thuộc tính" help_text: "Văn bản trợ giúp" - caption: "Caption" + caption: "chú thích" auth_provider: - scim_clients: "SCIM clients" + scim_clients: "khách hàng SCIM" calculated_value_error: - error_code: "Error code" - customized: "Customized" - customized_id: "Customized ID" - customized_type: "Customized type" + error_code: "Mã lỗi" + customized: "tùy chỉnh" + customized_id: "ID tùy chỉnh" + customized_type: "Loại tùy chỉnh" capability: - context: "Context" + context: "Bối cảnh" changeset: repository: "Kho lưu trữ" comment: commented: "Nhận xét" #an object that this comment belongs to custom_action: - actions: "Hành động" + actions: "hành động" custom_field: allow_non_open_versions: "Cho phép các phiên bản không mở" default_value: "Giá trị mặc định" editable: "Có thể chỉnh sửa" field_format: "Định dạng" - formula: "Formula" + formula: "Công thức" is_filter: "Dùng như bộ lọc" - is_for_all: "Cho tất cả các dự án" + is_for_all: "Đối với tất cả các dự án" is_required: "Bắt buộc" max_length: "Chiều dài tối đa" min_length: "Chiều dài tối thiểu" - content_right_to_left: "Right-to-Left content" + content_right_to_left: "Nội dung từ phải sang trái" multi_value: "Cho phép nhiều lựa chọn" possible_values: "Giá trị có thể" regexp: "Biểu thức chính quy" @@ -1199,30 +1197,30 @@ vi: custom_value: value: "Giá trị" design_color: - variable: "Variable" + variable: "Biến" doorkeeper/application: uid: "ID Khách hàng" secret: "Bí mật khách hàng" owner: "Người sở hữu" - builtin: "Builtin" + builtin: "Nội dung" enabled: "Đang hoạt động" redirect_uri: "URI đổi hướng" - client_credentials_user_id: "ID người dùng thông tin xác thực của khách hàng" + client_credentials_user_id: "ID người dùng thông tin xác thực khách hàng" scopes: "Phạm vi" confidential: "Bí mật" emoji_reaction: - reactable: "Reacted on" + reactable: "Đã phản hồi trên" enterprise_token: starts_at: "Có hiệu lực kể từ" subscriber: "Người đăng ký" - subscription: "Đăng ký" - plan: "Gói dịch vụ" + subscription: "đăng ký" + plan: "kế hoạch" encoded_token: "Mã thông báo hỗ trợ doanh nghiệp" - active_user_count_restriction: "Active users" + active_user_count_restriction: "Người dùng đang hoạt động" enterprise_trial: - company: "Công ty" + company: "công ty" favorite: - favorited: "Item" + favorited: "mục" grids/grid: page: "Trang" row_count: "Số dòng" @@ -1230,208 +1228,208 @@ vi: widgets: "Tiện ích" journal: notes: "Ghi chú" - cause_type: "Cause type" + cause_type: "Loại nguyên nhân" ldap_auth_source: - account: "Tài khoản" + account: "tài khoản" attr_firstname: "Thuộc tính tên" attr_lastname: "Thuộc tính Họ" attr_login: "Thuộc tính tên người dùng" attr_mail: "Thuộc tính Email" - filter_string: "Filter string" - admin: "Người Quản lý" - base_dn: "DN căn bản" - host: "Máy chủ" + filter_string: "Chuỗi bộ lọc" + admin: "quản trị viên" + base_dn: "DN cơ sở" + host: "chủ nhà" onthefly: "Tự động tạo người dùng" - port: "Cổng" + port: "hải cảng" tls_certificate_string: "Chứng chỉ SSL của máy chủ LDAP" mcp_configuration: - enabled: Enabled - title: Title - description: Description + enabled: Đã bật + title: Tiêu đề + description: Mô tả member: roles: "Vai trò" notification: - read_ian: "Read in-app" - resource: "Resource" + read_ian: "Đọc trong ứng dụng" + resource: "tài nguyên" oauth_client: client: "ID người dùng" project: active_value: - true: "chưa lưu trữ" + true: "không được lưu trữ" false: "đã lưu trữ" - attribute_groups: "Attribute Groups" - description: "Mô tả" + attribute_groups: "Nhóm thuộc tính" + description: "mô tả" enabled_modules: "Các mô-đun đã bật" identifier: "Định danh" latest_activity_at: "Các hoạt động mới nhất tại" parent: "Dự án con của" - project_creation_wizard_enabled: "Project initiation request" + project_creation_wizard_enabled: "Yêu cầu khởi tạo dự án" public_value: - title: "Hiển thị" + title: "Khả năng hiển thị" true: "công khai" false: "riêng tư" queries: "Truy vấn" - status_code: "Status" - status_explanation: "Status description" + status_code: "trạng thái" + status_explanation: "Mô tả trạng thái" status_codes: not_started: "Chưa bắt đầu" - on_track: "Đang tiến triển" + on_track: "Đang đi đúng hướng" at_risk: "Có nguy cơ" - off_track: "Bị lệch" - finished: "Hoàn thành" - discontinued: "Ngừng hoạt động" - project_creation_wizard_assignee_custom_field: "Assignee when submitted" - project_creation_wizard_notification_text: "Notification text" - project_creation_wizard_send_confirmation_email: "Confirmation email" - project_creation_wizard_status_when_submitted: "Status when submitted" - project_creation_wizard_work_package_comment: "Work package comment" - project_creation_wizard_work_package_type: "Work package type" - template: "Template" + off_track: "Lạc lối" + finished: "Đã hoàn thành" + discontinued: "Đã ngừng sản xuất" + project_creation_wizard_assignee_custom_field: "Người được chuyển nhượng khi nộp" + project_creation_wizard_notification_text: "Văn bản thông báo" + project_creation_wizard_send_confirmation_email: "Email xác nhận" + project_creation_wizard_status_when_submitted: "Trạng thái khi gửi" + project_creation_wizard_work_package_comment: "Nhận xét gói công việc" + project_creation_wizard_work_package_type: "Loại gói công việc" + template: "mẫu" templated: "Dự án mẫu" templated_value: true: "được đánh dấu là mẫu" - false: "không được đánh dấu là mẫu" + false: "đã bỏ đánh dấu là mẫu" types: "Các loại" versions: "Các phiên bản" work_packages: "Work Packages" - workspace_type: "Workspace type" + workspace_type: "Loại không gian làm việc" project_custom_field: - custom_field_section: Phần + custom_field_section: phần subproject_template_assignment: - workspace_type: "Workspace type" + workspace_type: "Loại không gian làm việc" project/phase: - date_range: "Khoảng thời gian" - definition: "Definition" - duration: "Thời gian" + date_range: "Phạm vi ngày" + definition: "độ nét" + duration: "thời lượng" start_date: "Ngày bắt đầu" - start_date_caption: "Follows the previous phase." + start_date_caption: "Tiếp theo giai đoạn trước." finish_date: "Ngày kết thúc" project/phase_definition: - name: "Tên" - color: "Màu sắc" - start_gate: "Start phase gate" - start_gate_name: "Start phase gate name" - finish_gate: "Finish phase gate" - finish_gate_name: "Finish phase gate name" + name: "tên" + color: "màu sắc" + start_gate: "Cổng pha bắt đầu" + start_gate_name: "Tên cổng giai đoạn bắt đầu" + finish_gate: "Cổng giai đoạn hoàn thiện" + finish_gate_name: "Tên cổng giai đoạn kết thúc" query: - sums: "Sums" + sums: "Tổng" columns: "Cột" column_names: "Cột" relations_to_type_column: "Quan hệ với %{type}" relations_of_type_column: "%{type} quan hệ" - child_work_packages: "Child work packages" + child_work_packages: "Gói công việc trẻ em" group_by: "Nhóm kết quả bởi" - sort_by: "Sort results by" + sort_by: "Sắp xếp kết quả theo" filters: "Bộ lọc" timeline_labels: "Nhãn dòng thời gian" - timeline_visible: "Xem biểu đồ sự kiện" - timeline_zoom_level: "Gantt chart zoom level" - timestamps: "Baseline timestamps" - sort_criteria: "Sort criteria" - highlighted_attributes: "Highlighted attributes" - highlighting_mode: "Highlight mode" - display_representation: "Display mode" - show_hierarchies: "Display mode" - starred: "Yêu thích" - hidden: "Hidden" - manual_sorting: "Manual sort order" - ordered_work_packages: "Work packages order" - include_subprojects: "Bao gồm dự án con" - results: "Các kết quả" + timeline_visible: "Hiển thị biểu đồ Gantt" + timeline_zoom_level: "Mức thu phóng biểu đồ Gantt" + timestamps: "Dấu thời gian cơ sở" + sort_criteria: "Sắp xếp tiêu chí" + highlighted_attributes: "Thuộc tính nổi bật" + highlighting_mode: "Chế độ đánh dấu" + display_representation: "Chế độ hiển thị" + show_hierarchies: "Chế độ hiển thị" + starred: "yêu thích" + hidden: "Ẩn" + manual_sorting: "Thứ tự sắp xếp thủ công" + ordered_work_packages: "Đặt hàng gói công việc" + include_subprojects: "Bao gồm các tiểu dự án" + results: "kết quả" relation: - lag: "Kết quả chậm" - from: "Work package liên quan" + lag: "Độ trễ" + from: "Gói công việc liên quan" to: "Work package liên quan" - relation_type: "Relation type" + relation_type: "Kiểu quan hệ" reminder: - remindable: "Reminded object" - remind_at: "Remind at" - remind_at_date: "Ngày" - remind_at_time: "Thời gian" + remindable: "Đối tượng được nhắc nhở" + remind_at: "Nhắc nhở vào lúc" + remind_at_date: "ngày" + remind_at_time: "thời gian" reminder_notification: - notification: "Notification" + notification: "thông báo" repository: url: "Đường dẫn (URL)" role: - permissions: "Phân Quyền" + permissions: "quyền" scim_client: - authentication_method: "Authentication method" - jwt_sub: "Subject claim" + authentication_method: "Phương thức xác thực" + jwt_sub: "Chủ đề yêu cầu bồi thường" status: is_closed: "Work package đã đóng" is_readonly: "Work package chỉ đọc" - excluded_from_totals: "Loại trừ khỏi tính tổng trong phân cấp" + excluded_from_totals: "Loại trừ khỏi việc tính tổng theo thứ bậc" default_done_ratio: "% Hoàn thành" token/named: - token_name: "Token name" + token_name: "Tên mã thông báo" token/ical: - calendar: "Lịch" - ical_token_query_assignment: "Query assignment" + calendar: "lịch" + ical_token_query_assignment: "Phân công truy vấn" time_entry: activity: "Hoạt động" hours: "Giờ" spent_on: "Ngày" type: "Kiểu" - ongoing: "Đang diễn ra" + ongoing: "đang diễn ra" type: description: "Văn bản mặc định cho mô tả" - attribute_groups: "Mẫu cấu hình" + attribute_groups: "Cấu hình biểu mẫu" is_in_roadmap: "Hiển thị trong lộ trình mặc định" is_default: "Kích hoạt cho các dự án mới theo mặc định" - is_milestone: "Là cột mốc" + is_milestone: "Là cột mốc quan trọng" color: "Màu sắc" - patterns: "Patterns" + patterns: "mẫu" remote_identity: - auth_source: "Auth Source" - integration: "Integration" - user: "Người dùng" + auth_source: "Nguồn xác thực" + integration: "tích hợp" + user: "người dùng" user: admin: "Người Quản lý" auth_source: "Nguồn xác thực" ldap_auth_source: "Kết nối LDAP" - identity_url: "URL danh tính" + identity_url: "URL nhận dạng" current_password: "Mật khẩu hiện tại" force_password_change: "Buộc thay đổi mật khẩu trong lần đăng nhập tiếp theo" language: "Ngôn ngữ" last_login_on: "Lần đăng nhập trước" - failed_login_count: "Failed login attempts" + failed_login_count: "Số lần đăng nhập không thành công" first_name: "Tên" last_name: "Họ" - first_login: "First login" + first_login: "Đăng nhập lần đầu" new_password: "Mật khẩu mới" password_confirmation: "Xác nhận lại mật khẩu" consented_at: "Đồng ý tại" group: - identity_url: "URL danh tính" + identity_url: "URL nhận dạng" user_preference: - header_look_and_feel: "Look and feel" - header_alerts: "Alerts" - button_update_look_and_feel: "Update look and feel" - button_update_alerts: "Update alerts" - button_update_user_information: "Update profile" - comments_sorting: "Display work package activity sorted by" - disable_keyboard_shortcuts: "Disable keyboard shortcuts" + header_look_and_feel: "Nhìn và cảm nhận" + header_alerts: "cảnh báo" + button_update_look_and_feel: "Cập nhật giao diện" + button_update_alerts: "Cập nhật cảnh báo" + button_update_user_information: "Cập nhật hồ sơ" + comments_sorting: "Hiển thị hoạt động gói công việc được sắp xếp theo" + disable_keyboard_shortcuts: "Tắt phím tắt" disable_keyboard_shortcuts_caption_html: |- - You can choose to disable default keyboard shortcuts if you use a screen reader or want to avoid accidentally triggering an action with a shortcut. - dismissed_enterprise_banners: "Hidden enterprise banners" + Bạn có thể tắt các phím tắt mặc định nếu bạn sử dụng trình đọc màn hình hoặc muốn tránh vô tình kích hoạt một hành động bằng phím tắt. + dismissed_enterprise_banners: "Biểu ngữ doanh nghiệp ẩn" impaired: "Viet nam" - auto_hide_popups: "Automatically hide success banners" - auto_hide_popups_caption: "When enabled, the green success banners will automatically disappear after 5 seconds." - warn_on_leaving_unsaved: "Cảnh báo tôi khi rời khỏi gói công việc với thay đổi chưa lưu" - increase_theme_contrast: "Increase theme contrast" - increase_contrast: "Increase contrast" - increase_contrast_caption: "Enables high-contrast mode for the chosen colour mode." - force_light_theme_contrast: "Force high-contrast when in Light mode" - force_dark_theme_contrast: "Force high-contrast when in Dark mode" - force_light_theme_contrast_caption: "Uses the high-contrast version of Light mode when automatic color mode is selected." - force_dark_theme_contrast_caption: "Uses the high-contrast version of Dark mode when automatic color mode is selected." - theme: "Color mode" + auto_hide_popups: "Tự động ẩn biểu ngữ thành công" + auto_hide_popups_caption: "Khi được bật, biểu ngữ thành công màu xanh sẽ tự động biến mất sau 5 giây." + warn_on_leaving_unsaved: "Cảnh báo tôi khi rời khỏi gói công việc có những thay đổi chưa được lưu" + increase_theme_contrast: "Tăng độ tương phản chủ đề" + increase_contrast: "Tăng độ tương phản" + increase_contrast_caption: "Bật chế độ tương phản cao cho chế độ màu đã chọn." + force_light_theme_contrast: "Buộc độ tương phản cao khi ở chế độ Ánh sáng" + force_dark_theme_contrast: "Buộc độ tương phản cao khi ở chế độ Tối" + force_light_theme_contrast_caption: "Sử dụng phiên bản có độ tương phản cao của Chế độ ánh sáng khi chế độ màu tự động được chọn." + force_dark_theme_contrast_caption: "Sử dụng phiên bản có độ tương phản cao của Chế độ tối khi chọn chế độ màu tự động." + theme: "Chế độ màu" time_zone: "Múi giờ" - mode_guideline: "Some modes will overwrite custom theme colors for accessibility and legibility. Please select Light mode for full custom theme support." - daily_reminders: "Daily reminders" + mode_guideline: "Một số chế độ sẽ ghi đè màu chủ đề tùy chỉnh để đảm bảo khả năng truy cập và mức độ dễ đọc. Vui lòng chọn Chế độ ánh sáng để được hỗ trợ chủ đề tùy chỉnh đầy đủ." + daily_reminders: "Lời nhắc hàng ngày" workdays: "Ngày làm việc" users/invitation/form_model: - principal_type: "Invitation type" + principal_type: "Loại lời mời" id_or_email: "Tên hoặc địa chỉ email" version: effective_date: "Ngày hoàn thành" @@ -1441,150 +1439,150 @@ vi: wiki_page: parent_title: "Trang mẹ" redirect_existing_links: "Chuyển hướng các liên kết hiện có" - text: "Page content" + text: "Nội dung trang" work_package: - ancestor: "Descendants of" #used for filtering of work packages that are descendants of a given work package + ancestor: "Hậu duệ của" #used for filtering of work packages that are descendants of a given work package begin_insertion: "Bắt đầu chèn" begin_deletion: "Bắt đầu xóa" children: "Subelements" derived_done_ratio: "Tổng % hoàn thành" - derived_remaining_hours: "Tổng thời gian còn lại" - derived_remaining_time: "Tổng số giờ còn lại" + derived_remaining_hours: "Tổng số công việc còn lại" + derived_remaining_time: "Tổng số công việc còn lại" done_ratio: "% Hoàn thành" - duration: "Thời gian" + duration: "thời lượng" end_insertion: "Kết thúc chèn" end_deletion: "Kết thúc quá trình xóa" - ignore_non_working_days: "Bỏ qua ngày không làm việc" + ignore_non_working_days: "Bỏ qua những ngày không làm việc" include_non_working_days: title: "Ngày làm việc" false: "chỉ ngày làm việc" - true: "bao gồm ngày không làm việc" - journal_internal: Internal Journal - notify: "Thông báo" #used in custom actions + true: "bao gồm những ngày không làm việc" + journal_internal: Tạp chí nội bộ + notify: "thông báo" #used in custom actions parent: "Cha" - parent_issue: "Cha" - parent_work_package: "Cha" + parent_issue: "cha mẹ" + parent_work_package: "cha mẹ" priority: "Độ ưu tiên" progress: "% Hoàn thành" readonly: "Chỉ đọc" remaining_hours: "Công việc còn lại" - remaining_time: "Thời gian còn lại" - shared_with_users: "Chia sẻ với" + remaining_time: "Công việc còn lại" + shared_with_users: "Được chia sẻ với" schedule_manually: "Lên lịch thủ công" - spent_hours: "Thời gian" + spent_hours: "dành thời gian" spent_time: "Thời gian" subproject: "Dự án con" time_entries: "Thời gian truy cập" - type: "Kiểu" + type: "loại" version: "Phiên bản" watcher: "Người quan sát" errors: messages: accepted: "phải được đồng ý" after: "phải sau %{date}" - after_today: "must be in the future." + after_today: "phải ở tương lai." after_or_equal_to: "phải sau hoặc tương đương với %{date}" before: "phải trước khi %{date}" before_or_equal_to: "phải có trước hay tương đương với %{date}" blank: "không được để trống" - blank_nested: "cần có thuộc tính '%{property}' được thiết lập." - cannot_delete_mapping: "là bắt buộc. Không thể xóa." - is_for_all_cannot_modify: "is for all projects and can therefore not be modified." - cant_link_a_work_package_with_a_descendant: "Một gói công việc không thể liên kết với một trong các nhiệm vụ con của nó." - circular_dependency: "Mối quan hệ này sẽ tạo ra một sự phụ thuộc tuần hoàn." + blank_nested: "cần phải đặt thuộc tính '%{property}'." + cannot_delete_mapping: "được yêu cầu. Không thể xóa được." + is_for_all_cannot_modify: "dành cho tất cả các dự án và do đó không thể sửa đổi được." + cant_link_a_work_package_with_a_descendant: "Một gói công việc không thể được liên kết với một trong các nhiệm vụ con của nó." + circular_dependency: "Mối quan hệ này sẽ tạo ra sự phụ thuộc vòng tròn." confirmation: "không khớp %{attribute}." - could_not_be_copied: "%{dependency} không thể được sao chép (hoàn toàn)." + could_not_be_copied: "%{dependency} không thể sao chép (đầy đủ)." does_not_exist: "không tồn tại" - error_enterprise_only: "%{action} is only available in the OpenProject Enterprise edition." + error_enterprise_only: "%{action} chỉ có trong phiên bản OpenProject Enterprise." error_unauthorized: "Có thể không được truy cập." - error_readonly: "đã bị ghi lại nhưng không thể ghi." - error_conflict: "Thông tin đã được cập nhật bởi ít nhất một người dùng khác trong thời gian này." - error_not_found: "not found." - email: "không phải là địa chỉ email hợp lệ." + error_readonly: "đã được cố gắng viết nhưng không thể ghi được." + error_conflict: "Thông tin đã được cập nhật bởi ít nhất một người dùng khác trong thời gian chờ đợi." + error_not_found: "không tìm thấy." + email: "không phải là một địa chỉ email hợp lệ." empty: "không thể để trống" - enterprise_plan_required: "requires at least the %{plan_name}." - even: "phải là số chẵn." + enterprise_plan_required: "yêu cầu ít nhất %{plan_name}." + even: "phải chẵn." exclusion: "được bảo lưu." - feature_disabled: is not available. - feature_disabled_for_project: is disabled for this project. + feature_disabled: không có sẵn. + feature_disabled_for_project: đã bị vô hiệu hóa cho dự án này. file_too_large: "quá lớn (kích thước tối đa là %{count} bytes)" filter_does_not_exist: "bộ lọc không tồn tại." - format: "không khớp với định dạng mong đợi '%{expected}'." - format_nested: "không khớp với định dạng mong đợi '%{expected}' tại đường dẫn '%{path}'." + format: "không khớp với định dạng dự kiến ​​'%{expected}'." + format_nested: "không khớp với định dạng dự kiến ​​'%{expected}' tại đường dẫn '%{path}'." greater_than: "phải lớn hơn %{count}" greater_than_or_equal_to: "phải lớn hơn hoặc bằng %{count}" greater_than_or_equal_to_start_date: "phải lớn hơn hoặc bằng ngày bắt đầu." greater_than_start_date: "phải lớn hơn ngày bắt đầu." inclusion: "không nằm trong các giá trị cho phép" - inclusion_nested: "không được thiết lập thành một trong các giá trị được phép tại đường dẫn '%{path}'." + inclusion_nested: "không được đặt thành một trong các giá trị được phép tại đường dẫn '%{path}'." invalid: "không hợp lệ" invalid_url: "không phải là một URL hợp lệ." invalid_url_scheme: "không phải là giao thức được hỗ trợ (được phép: %{allowed_schemes})." less_than_or_equal_to: "phải nhỏ hơn hoặc bằng %{count}" - not_available: "không có sẵn do cấu hình hệ thống." - not_deletable: "không thể bị xóa." + not_available: "không khả dụng do cấu hình hệ thống." + not_deletable: "không thể xóa được." not_current_user: "không phải là người dùng hiện tại." - not_found: "not found." + not_found: "không tìm thấy." not_a_date: "không phải là ngày hợp lệ" not_a_datetime: "không phải là thời gian hợp lệ" not_a_number: "không phải là số" not_allowed: "không hợp lệ vì thiếu quyền." - not_json: "is not parseable as JSON." - not_json_object: "is not a JSON object." + not_json: "không thể phân tích cú pháp dưới dạng JSON." + not_json_object: "không phải là một đối tượng JSON." not_an_integer: "không phải là một số nguyên" not_an_iso_date: "không phải là một ngày hợp lệ. Yêu cầu định dạng: YYYY-MM-DD." not_same_project: "không thuộc cùng dự án." - datetime_must_be_in_future: "must be in the future." + datetime_must_be_in_future: "phải ở tương lai." odd: "phải là số lẻ" regex_match_failed: "không khớp với biểu thức chính quy %{expression}." regex_invalid: "có thể không được xác nhận với các biểu hiện thường xuyên liên kết." - regex_list_invalid: "Lines %{invalid_lines} could not be parsed as regular expression." - hexcode_invalid: "is not a valid 6-digit hexadecimal color code." + regex_list_invalid: "Không thể phân tích cú pháp các dòng %{invalid_lines} dưới dạng biểu thức chính quy." + hexcode_invalid: "không phải là mã màu thập lục phân 6 chữ số hợp lệ." smaller_than_or_equal_to_max_length: "phải nhỏ hơn hoặc bằng với chiều dài tối đa" taken: "đã bị dùng rồi." too_long: "quá dài (tối đa %{count} ký tự)." too_short: "quá ngắn (tối thiểu %{count} ký tự)." - type_mismatch: "không phải kiểu '%{type}'" - type_mismatch_nested: "không phải kiểu '%{type}' tại đường dẫn '%{path}'" + type_mismatch: "không thuộc loại '%{type}'" + type_mismatch_nested: "không thuộc loại '%{type}' tại đường dẫn '%{path}'" unchangeable: "không thể thay đổi." - unknown_property: "không phải là thuộc tính được biết đến." + unknown_property: "không phải là một tài sản được biết đến." unknown_property_nested: "có đường dẫn không xác định '%{path}'." unremovable: "không thể gỡ bỏ." url_not_secure_context: > - không cung cấp "Bối cảnh Bảo mật". Vui lòng sử dụng HTTPS hoặc địa chỉ vòng lặp, chẳng hạn như localhost. + không cung cấp "Bối cảnh an toàn". Sử dụng HTTPS hoặc địa chỉ loopback, chẳng hạn như localhost. wrong_length: "độ dài không đúng (phải là %{count} ký tự)." models: ldap_auth_source: attributes: tls_certificate_string: - invalid_certificate: "Chứng chỉ SSL cung cấp không hợp lệ: %{additional_message}" + invalid_certificate: "Chứng chỉ SSL được cung cấp không hợp lệ: %{additional_message}" format: "%{message}" attachment: attributes: content_type: - blank: "Loại nội dung của tệp không thể để trống." - not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload." + blank: "Loại nội dung của tập tin không được để trống." + not_allowlisted: "Tệp đã bị bộ lọc tự động từ chối. '%{value}' không được phép tải lên." format: "%{message}" capability: context: - global: "Toàn cầu" + global: "toàn cầu" query: filters: - minimum: "cần bao gồm ít nhất một bộ lọc cho principal, context hoặc id với toán tử '='." + minimum: "cần bao gồm ít nhất một bộ lọc cho giá trị chính, ngữ cảnh hoặc id bằng toán tử '='." custom_field: at_least_one_custom_option: "Ít nhất một lựa chọn cần phải được cung cấp." - previous_custom_field_recalculation_unprocessed: "The recalculation of previous changes for this custom field have not been applied yet, please try again in a few minutes." + previous_custom_field_recalculation_unprocessed: "Tính toán lại các thay đổi trước đó cho trường tùy chỉnh này chưa được áp dụng, vui lòng thử lại sau vài phút." referenced_in_other_fields_html: - other: "%{name} is used in project attribute calculations: %{links}." + other: "%{name} được sử dụng trong tính toán thuộc tính dự án: %{links}." attributes: formula: - blank: "Formula can't be blank." - invalid: "Formula is invalid." - invalid_characters: "Only numeric values, mathematical operators and project attributes of type integer, float, calculated value and weighted list are allowed." - not_allowed_custom_fields_referenced: "The attribute %{custom_fields} cannot be used because it leads to a circular reference; one attribute depends on the other." + blank: "Công thức không được để trống." + invalid: "Công thức không hợp lệ." + invalid_characters: "Chỉ cho phép các giá trị số, toán tử toán học và thuộc tính dự án thuộc loại số nguyên, số float, giá trị được tính toán và danh sách có trọng số." + not_allowed_custom_fields_referenced: "Thuộc tính %{custom_fields} không thể được sử dụng vì nó dẫn đến tham chiếu vòng tròn; một thuộc tính phụ thuộc vào thuộc tính khác." format: "%{message}" required: - cannot_be_true: "cannot be set to true." + cannot_be_true: "không thể được đặt thành đúng." custom_fields_project: attributes: project_ids: @@ -1604,20 +1602,20 @@ vi: fragment_present: "không thể chứa một đoạn." invalid_uri: "phải là một URI hợp lệ." relative_uri: "phải là một URI tuyệt đối." - secured_uri: 'không cung cấp "Bối cảnh Bảo mật". Vui lòng sử dụng HTTPS hoặc địa chỉ vòng lặp, chẳng hạn như localhost.' + secured_uri: 'không cung cấp "Bối cảnh an toàn". Sử dụng HTTPS hoặc địa chỉ loopback, chẳng hạn như localhost.' forbidden_uri: "bị chặn bởi máy chủ." scopes: not_match_configured: "không phù hợp bất cứ phạm vi nào." enterprise_trial: - already_used: "was already used to create a trial." - failed_to_create: "Trial could not be created (%{status})" - general_consent: "Please accept the terms and conditions." + already_used: "đã được sử dụng để tạo bản dùng thử." + failed_to_create: "Không thể tạo bản dùng thử (%{status})" + general_consent: "Vui lòng chấp nhận các điều khoản và điều kiện." enterprise_token: - only_one_trial: "Only one trial token can be active. Please delete the previous trial token before adding another." + only_one_trial: "Chỉ có một mã thông báo dùng thử có thể hoạt động. Vui lòng xóa mã thông báo dùng thử trước đó trước khi thêm mã thông báo khác." unreadable: "không thể đọc được. Bạn có chắc đó là mã thông báo hỗ trợ không?" - already_added: "This token has already been added." + already_added: "Mã thông báo này đã được thêm vào." favorite: - already_favorited: "has already been favorited." + already_favorited: "đã được yêu thích rồi." grids/grid: overlaps: "chồng lấn." outside: "nằm ngoài bảng dữ liệu." @@ -1626,24 +1624,24 @@ vi: attributes: name: blank: "là bắt buộc. Vui lòng chọn một tên." - not_unique: "đã được sử dụng. Vui lòng chọn một tên khác." + not_unique: "đã được sử dụng. Vui lòng chọn tên khác." meeting: - error_conflict: "Unable to save because the meeting was updated by someone else in the meantime. Please reload the page." + error_conflict: "Không thể lưu vì cuộc họp đã được người khác cập nhật trong thời gian chờ đợi. Vui lòng tải lại trang." notifications: - at_least_one_channel: "Cần chỉ định ít nhất một kênh để gửi thông báo." + at_least_one_channel: "Cần phải chỉ định ít nhất một kênh để gửi thông báo." attributes: read_ian: - read_on_creation: "không thể được thiết lập thành true khi tạo thông báo." + read_on_creation: "không thể đặt thành true khi tạo thông báo." mail_reminder_sent: - set_on_creation: "không thể được thiết lập thành true khi tạo thông báo." + set_on_creation: "không thể đặt thành true khi tạo thông báo." reason: - no_notification_reason: "không thể để trống khi IAN được chọn làm kênh." + no_notification_reason: "không thể để trống vì IAN được chọn làm kênh." reason_mail_digest: - no_notification_reason: "không thể để trống khi mail digest được chọn làm kênh." + no_notification_reason: "không được để trống vì thông báo thư được chọn làm kênh." non_working_day: attributes: date: - taken: "Một ngày không làm việc đã tồn tại cho %{value}." + taken: "Đã tồn tại một ngày không làm việc cho %{value}." format: "%{message}" parse_schema_filter_params_service: attributes: @@ -1654,15 +1652,15 @@ vi: project: archived_ancestor: "Dự án có một khu vực lưu trữ" foreign_wps_reference_version: "Các gói công việc trong các phiên bản tham chiếu không kế thừa của dự án hoặc kế thừa nó." - cannot_be_assigned_to_artifact_work_package: "The chosen user is not allowed to be assigned to work packages." + cannot_be_assigned_to_artifact_work_package: "Người dùng đã chọn không được phép gán cho các gói công việc." attributes: base: archive_permission_missing_on_subprojects: "Bạn không có quyền cần thiết để lưu trữ tất cả các dự án con. Vui lòng liên hệ với quản trị viên." - project_initiation_request_disabled: "Project initiation request is disabled. It must be enabled to create the artifact work package." + project_initiation_request_disabled: "Yêu cầu khởi tạo dự án bị vô hiệu hóa. Nó phải được kích hoạt để tạo gói công việc tạo tác." types: in_use_by_work_packages: "gói công việc vẫn được sử dụng: %{types}" enabled_modules: - dependency_missing: "Mô-đun '%{dependency}' cũng cần được bật vì mô-đun '%{module}' phụ thuộc vào nó." + dependency_missing: "Mô-đun '%{dependency}' cũng cần được bật vì mô-đun '%{module}' phụ thuộc vào mô-đun đó." format: "%{message}" project_custom_field_project_mapping: attributes: @@ -1671,11 +1669,11 @@ vi: project/phase: attributes: start_date: - must_be_before_finish_date: "must be before the finish date." - non_continuous_dates: "can't be earlier than the previous phase's end date." + must_be_before_finish_date: "phải trước ngày kết thúc." + non_continuous_dates: "không thể sớm hơn ngày kết thúc của giai đoạn trước." finish_date: - must_be_after_start_date: "must be after the start date." - cannot_be_a_non_working_day: "can't be a non-working day." + must_be_after_start_date: "phải sau ngày bắt đầu." + cannot_be_a_non_working_day: "không thể là một ngày không làm việc." query: attributes: public: @@ -1690,45 +1688,45 @@ vi: invalid: "Không thể sắp xếp theo cột: %{value}" format: "%{message}" timestamps: - invalid: "Dấu thời gian chứa các giá trị không hợp lệ: %{values}" - forbidden: "Các dấu thời gian chứa các giá trị bị cấm: %{values}" + invalid: "Dấu thời gian chứa giá trị không hợp lệ: %{values}" + forbidden: "Dấu thời gian chứa các giá trị bị cấm: %{values}" format: "%{message}" selects: - name_not_included: "Cột 'Tên' cần phải được bao gồm" + name_not_included: "Cột 'Tên' cần được đưa vào" nonexistent: "Cột '%{column}' không tồn tại." format: "%{message}" - group_by_hierarchies_exclusive: "xung đột với nhóm theo '%{group_by}'. Bạn không thể kích hoạt cả hai." - can_only_be_modified_by_owner: "Truy vấn chỉ có thể được chỉnh sửa bởi chủ sở hữu của nó." - need_permission_to_modify_public_query: "Bạn không thể chỉnh sửa một truy vấn công khai." + group_by_hierarchies_exclusive: "loại trừ lẫn nhau với nhóm bởi '%{group_by}'. Bạn không thể kích hoạt cả hai." + can_only_be_modified_by_owner: "Truy vấn chỉ có thể được sửa đổi bởi chủ sở hữu của nó." + need_permission_to_modify_public_query: "Bạn không thể sửa đổi truy vấn công khai." filters: custom_fields: inexistent: "Không có trường tùy chỉnh cho bộ lọc." queries/filters/base: attributes: values: - inclusion: "bộ lọc có các giá trị không hợp lệ." + inclusion: "bộ lọc có giá trị không hợp lệ." format: "%{message}" queries/principals/filters/internal_mentionable_on_work_package_filter: attributes: values: - single_value_requirement: "must be a single work package" + single_value_requirement: "phải là một gói công việc duy nhất" relation: typed_dag: circular_dependency: "Mối quan hệ tạo ra một vòng tròn các mối quan hệ." attributes: base: - error_not_deletable: "This relation cannot be deleted because you do not have edit permissions for the selected work package." - error_not_editable: "This relation cannot be edited because you do not have edit permissions for the selected work package." + error_not_deletable: "Không thể xóa mối quan hệ này vì bạn không có quyền chỉnh sửa đối với gói công việc đã chọn." + error_not_editable: "Không thể chỉnh sửa mối quan hệ này vì bạn không có quyền chỉnh sửa đối với gói công việc đã chọn." to_id: - format: "The selected work package %{message}" - error_not_found: "could not be found." - error_readonly: "cannot be changed for existing relations." - error_not_manageable: "cannot be added because you do not have edit permissions for the selected work package." + format: "Gói công việc đã chọn %{message}" + error_not_found: "không thể tìm thấy." + error_readonly: "không thể thay đổi các mối quan hệ hiện có." + error_not_manageable: "không thể thêm vì bạn không có quyền chỉnh sửa đối với gói công việc đã chọn." from_id: - format: "The selected work package %{message}" - error_not_found: "could not be found." - error_readonly: "cannot be changed for existing relations." - error_not_manageable: "cannot be added because you do not have edit permissions for the selected work package." + format: "Gói công việc đã chọn %{message}" + error_not_found: "không thể tìm thấy." + error_readonly: "không thể thay đổi các mối quan hệ hiện có." + error_not_manageable: "không thể thêm vì bạn không có quyền chỉnh sửa đối với gói công việc đã chọn." repository: not_available: "Nhà cung cấp SCM không có sẵn" not_whitelisted: "không được cấu hình cho phép." @@ -1738,20 +1736,20 @@ vi: role: attributes: permissions: - dependency_missing: "cần bao gồm '%{dependency}' vì '%{permission}' được chọn." + dependency_missing: "cũng cần bao gồm '%{dependency}' vì '%{permission}' được chọn." setting: attributes: base: - working_days_are_missing: "Ít nhất một ngày trong tuần phải được định nghĩa là ngày làm việc." - previous_working_day_changes_unprocessed: "Các thay đổi trước đó về cấu hình ngày làm việc chưa được áp dụng." - hours_per_day_are_missing: "Số giờ mỗi ngày phải được định nghĩa." - durations_are_not_positive_numbers: "Thời gian phải là các số dương." - hours_per_day_is_out_of_bounds: "Số giờ mỗi ngày không thể vượt quá 24" + working_days_are_missing: "Ít nhất một ngày trong tuần phải được xác định là ngày làm việc." + previous_working_day_changes_unprocessed: "Những thay đổi trước đó đối với cấu hình ngày làm việc vẫn chưa được áp dụng." + hours_per_day_are_missing: "Số giờ mỗi ngày phải được xác định." + durations_are_not_positive_numbers: "Khoảng thời gian phải là số dương." + hours_per_day_is_out_of_bounds: "Số giờ mỗi ngày không thể nhiều hơn 24" status: attributes: default_done_ratio: inclusion: "phải nằm trong khoảng từ 0 đến 100." - readonly_default_exlusive: "không thể được kích hoạt cho các trạng thái được đánh dấu là mặc định." + readonly_default_exlusive: "không thể kích hoạt cho các trạng thái được đánh dấu mặc định." time_entry: attributes: hours: @@ -1759,21 +1757,21 @@ vi: user_preference: attributes: pause_reminders: - invalid_range: "chỉ có thể là một khoảng thời gian hợp lệ." + invalid_range: "chỉ có thể là một phạm vi ngày hợp lệ." daily_reminders: - full_hour: "chỉ có thể được cấu hình để được gửi vào giờ tròn." + full_hour: "chỉ có thể được cấu hình để gửi vào một giờ đầy đủ." notification_settings: - only_one_global_setting: "Phải có chỉ một cài đặt thông báo toàn cầu." - email_alerts_global: "Cài đặt thông báo qua email chỉ có thể được thiết lập toàn cầu." + only_one_global_setting: "Chỉ được có một cài đặt thông báo chung." + email_alerts_global: "Cài đặt thông báo qua email chỉ có thể được đặt trên toàn cầu." format: "%{message}" wrong_date: "Giá trị sai cho Ngày bắt đầu, Ngày đến hạn hoặc Quá hạn." watcher: attributes: user_id: not_allowed_to_view: "không được phép xem tài nguyên này." - locked: "bị khóa." + locked: "đã bị khóa." wiki_page: - error_conflict: "Trang wiki đã được cập nhật bởi người khác trong khi bạn đang chỉnh sửa." + error_conflict: "Trang wiki đã được người khác cập nhật trong khi bạn đang chỉnh sửa nó." attributes: slug: undeducible: "không thể suy ra từ tiêu đề '%{title}'." @@ -1782,35 +1780,35 @@ vi: attributes: id: format: "%{message}" - cannot_add_child_because_of_lack_of_permission: "Cannot add child because you don't have permissions to edit the selected work package." - blank: "ID can't be blank." + cannot_add_child_because_of_lack_of_permission: "Không thể thêm con vì bạn không có quyền chỉnh sửa gói công việc đã chọn." + blank: "ID không được để trống." assigned_to: format: "%{message}" done_ratio: does_not_match_work_and_remaining_work: "không khớp với Công việc và Công việc còn lại" - cannot_be_set_when_work_is_zero: "không thể thiết lập khi Công việc là 0h" - must_be_set_when_remaining_work_is_set: "cần thiết khi công việc còn lại được thiết lập." - must_be_set_when_work_and_remaining_work_are_set: "cần thiết khi Công việc và Công việc còn lại được thiết lập." + cannot_be_set_when_work_is_zero: "không thể đặt khi Công việc là 0h" + must_be_set_when_remaining_work_is_set: "được yêu cầu khi Công việc còn lại được đặt." + must_be_set_when_work_and_remaining_work_are_set: "được yêu cầu khi Công việc và Công việc còn lại được đặt." inclusion: "phải nằm trong khoảng từ 0 đến 100." due_date: not_start_date: "không phải ngày bắt đầu, mặc dù điều này là cần thiết cho các mốc quan trọng." - cannot_be_null: "không thể được đặt thành null vì ngày bắt đầu và thời gian là đã biết." + cannot_be_null: "không thể được đặt thành null vì ngày bắt đầu và thời lượng đã biết." duration: - larger_than_dates: "lớn hơn khoảng thời gian giữa ngày bắt đầu và ngày kết thúc." - smaller_than_dates: "nhỏ hơn khoảng thời gian giữa ngày bắt đầu và ngày kết thúc." - not_available_for_milestones: "không khả dụng cho các gói công việc kiểu cột mốc." - cannot_be_null: "không thể được đặt thành null vì ngày bắt đầu và ngày kết thúc đã được biết." - not_an_integer: "không phải là thời gian hợp lệ." + larger_than_dates: "lớn hơn khoảng cách giữa ngày bắt đầu và ngày kết thúc." + smaller_than_dates: "nhỏ hơn khoảng cách giữa ngày bắt đầu và ngày kết thúc." + not_available_for_milestones: "không có sẵn cho các gói công việc được đánh dấu theo mốc quan trọng." + cannot_be_null: "không thể được đặt thành null vì ngày bắt đầu và ngày kết thúc đã biết." + not_an_integer: "không phải là một khoảng thời gian hợp lệ." parent: cannot_be_milestone: "không thể là một cột mốc." - cannot_be_self_assigned: "không thể tự phân bổ cho chính nó." + cannot_be_self_assigned: "không thể gán cho chính nó." cannot_be_in_another_project: "không thể có trong một dự án khác." not_a_valid_parent: "không hợp lệ" schedule_manually: - cannot_be_automatically_scheduled: "cannot be set to false (automatically scheduled) as it has no predecessors or children." + cannot_be_automatically_scheduled: "không thể được đặt thành sai (được lên lịch tự động) vì nó không có tiền thân hoặc con." start_date: violates_relationships: "chỉ có thể được thiếp lập sang %{soonest_start} hoặc mới hơn để không làm vi phạm các mối quan hệ của work package." - cannot_be_null: "không thể được đặt thành null vì ngày kết thúc và thời gian đã được biết." + cannot_be_null: "không thể được đặt thành null vì ngày kết thúc và thời lượng đã biết." status_id: status_transition_invalid: "không hợp lệ vì không tồn tại chuyển đổi hợp lệ từ trạng thái cũ sang trạng thái mới cho vai trò của người dùng hiện tại." status_invalid_in_type: "không hợp lệ vì tình trạng hiện tại không tồn tại loại này." @@ -1822,44 +1820,44 @@ vi: only_same_project_categories_allowed: "Các thể loại của một work package phải trong dự án tương tự như work package." does_not_exist: "Thể loại đã chỉ định không tồn tại." estimated_hours: - not_a_number: "không phải là thời gian hợp lệ." - cant_be_inferior_to_remaining_work: "không thể thấp hơn Công việc còn lại." - must_be_set_when_remaining_work_and_percent_complete_are_set: "bắt buộc khi thiết lập Công việc còn lại và % Hoàn thành." + not_a_number: "không phải là một khoảng thời gian hợp lệ." + cant_be_inferior_to_remaining_work: "không thể thấp hơn công việc còn lại." + must_be_set_when_remaining_work_and_percent_complete_are_set: "được yêu cầu khi đặt Công việc còn lại và % Hoàn thành." remaining_hours: - not_a_number: "không phải là thời gian hợp lệ." + not_a_number: "không phải là một khoảng thời gian hợp lệ." cant_exceed_work: "không thể cao hơn Công việc." - must_be_set_when_work_is_set: "cần thiết khi Công việc được thiết lập." - must_be_set_when_work_and_percent_complete_are_set: "bắt buộc khi Công việc và % Hoàn thành được thiết lập." - must_be_set_to_zero_hours_when_work_is_set_and_percent_complete_is_100p: '>- must be 0h when Work is set and % Complete is 100%.' + must_be_set_when_work_is_set: "được yêu cầu khi Công việc được đặt." + must_be_set_when_work_and_percent_complete_are_set: "được yêu cầu khi Công việc và % Hoàn thành được đặt." + must_be_set_to_zero_hours_when_work_is_set_and_percent_complete_is_100p: '>- phải là 0h khi Công việc được đặt và % Hoàn thành là 100%.' must_be_empty_when_work_is_empty_and_percent_complete_is_100p: >- phải trống khi Công việc trống và % Hoàn thành là 100%. - readonly_status: "Gói công việc đang ở trạng thái chỉ đọc nên các thuộc tính của nó không thể bị thay đổi." + readonly_status: "Gói công việc ở trạng thái chỉ đọc nên các thuộc tính của nó không thể thay đổi." type: attributes: attribute_groups: attribute_unknown: "Thuộc tính gói công việc không hợp lệ được sử dụng." - attribute_unknown_name: "Thuộc tính gói công việc không hợp lệ được sử dụng: %{attribute}" + attribute_unknown_name: "Thuộc tính gói công việc được sử dụng không hợp lệ: %{attribute}" duplicate_group: "Nhóm tên '%{group}' được sử dụng nhiều hơn một lần. Tên nhóm phải là duy nhất." query_invalid: "Truy vấn nhúng '%{group}' là không hợp lệ: %{details}" - group_without_name: "Các nhóm không tên không được phép." + group_without_name: "Nhóm không có tên không được phép." patterns: - invalid_tokens: "One or more attributes inside the field are not valid. Please, fix the attributes before saving." + invalid_tokens: "Một hoặc nhiều thuộc tính bên trong trường này không hợp lệ. Vui lòng sửa các thuộc tính trước khi lưu." user: attributes: base: - user_limit_reached: "Đã đạt giới hạn người dùng. Không thể tạo thêm tài khoản trên kế hoạch hiện tại." - one_must_be_active: "Người dùng Quản trị không thể bị khóa/xóa. Ít nhất một quản trị viên phải hoạt động." + user_limit_reached: "Đã đạt đến giới hạn người dùng. Không thể tạo thêm tài khoản trên gói hiện tại." + one_must_be_active: "Người dùng quản trị không thể bị khóa/xóa. Ít nhất một quản trị viên phải hoạt động." password_confirmation: confirmation: "Xác nhận mật khẩu không khớp với mật khẩu." format: "%{message}" password: - weak: "Phải chứa các ký tự của các lớp sau (tối thiểu là %{min_count} %{all_count}): %{rules}" + weak: "Phải chứa các ký tự thuộc các lớp sau (ít nhất %{min_count} trong số %{all_count}): %{rules}" lowercase: "chữ thường (ví dụ như 'a')" uppercase: "chữ hoa (ví dụ như ' A')" numeric: "số (ví dụ như ' 1')" special: "đặc biệt (ví dụ như ' %')" reused: - other: "đã được sử dụng trước đây. Vui lòng chọn cái khác mà phải khác với %{count} cuối cùng của bạn." + other: "đã được sử dụng trước đây. Vui lòng chọn một cái khác với %{count} cuối cùng của bạn." match: confirm: "Xác nhận lại mật khẩu." description: "'Xác nhận mật khẩu ' phải trùng với dữ liệu trong mục 'Mật khẩu mới'." @@ -1873,62 +1871,62 @@ vi: ungrantable: "có một vai trò không thể chuyển nhượng." more_than_one: "có nhiều hơn một vai trò." principal: - unassignable: "không thể phân bổ cho một dự án." + unassignable: "không thể được chỉ định cho một dự án." version: - undeletable_archived_projects: "Phiên bản không thể bị xóa vì nó có các gói công việc gắn liền với nó." - undeletable_work_packages_attached: "Phiên bản không thể bị xóa vì nó có các gói công việc gắn liền với nó." + undeletable_archived_projects: "Không thể xóa phiên bản này vì nó có các gói công việc được đính kèm." + undeletable_work_packages_attached: "Không thể xóa phiên bản này vì nó có các gói công việc được đính kèm." token/named: attributes: token_name: - blank: "Please provide a token name" - in_use: "This token name is already in use, please select a different one" + blank: "Vui lòng cung cấp tên mã thông báo" + in_use: "Tên mã thông báo này đã được sử dụng, vui lòng chọn tên khác" format: "%{message}" template: body: "Vui lòng kiểm tra các mục sau đây:" header: - other: "%{count} lỗi ngăn không cho lưu %{model} này" + other: "%{count} lỗi đã cấm lưu %{model} này" models: - attachment: "Tệp" + attachment: "tập tin" attribute_help_text: - other: "Attribute help texts" + other: "Văn bản trợ giúp thuộc tính" auth_provider: - other: "Authentication providers" + other: "Nhà cung cấp xác thực" category: "Thể loại" - color: "Màu sắc" + color: "màu sắc" comment: "Nhận xét" custom_action: "Tác vụ tùy chỉnh" custom_field: "Tùy chỉnh mục" "doorkeeper/application": "Ứng dụng OAuth" enterprise_token: - other: "Enterprise tokens" - forum: "Diễn đàn" + other: "Mã thông báo doanh nghiệp" + forum: "diễn đàn" global_role: "Vai trò toàn cầu" group: "Nhóm" issue_priority: - other: "Priorities" - meeting_participant: "Meeting participant" + other: "ưu tiên" + meeting_participant: "Người tham gia cuộc họp" member: "Thành viên" news: "Tin tức" notification: - other: "Thông báo" - placeholder_user: "Người dùng tạm thời" + other: "thông báo" + placeholder_user: "Người dùng giữ chỗ" project: - other: "Các dự án" + other: "dự án" project_query: other: "Danh sách dự án" query: "Truy vấn riêng" - reminder: "Reminder" + reminder: "Lời nhắc" role: - other: "Vai trò" + other: "Vai trò" scim_client: - other: "SCIM clients" + other: "khách hàng SCIM" status: "Tình trạng work package" token/api: - other: Mã truy cập + other: Mã thông báo truy cập token/rss: - other: "RSS tokens" + other: "Mã thông báo RSS" type: - other: "Types" + other: "các loại" user: "Người dùng" version: "Phiên bản" workflow: "Quy trình làm việc" @@ -1937,162 +1935,162 @@ vi: wiki_page: "Trang wiki" errors: header_invalid_fields: - other: "Đã có vấn đề với các mục sau đây:" + other: "Đã xảy ra sự cố với các trường sau:" header_additional_invalid_fields: - other: "Ngoài ra, còn có vấn đề với trường sau:" - field_erroneous_label: "Trường này không hợp lệ: %{full_errors}\nVui lòng nhập giá trị hợp lệ." + other: "Ngoài ra, còn có vấn đề với các trường sau:" + field_erroneous_label: "Trường này không hợp lệ: %{full_errors}\nVui lòng nhập một giá trị hợp lệ." messages: - must_be_template: "must be template" - unsupported_storage_type: "is not a supported storage type." - storage_error: "There was an error with the storage connection." - invalid_input: "The input is invalid." + must_be_template: "phải là mẫu" + unsupported_storage_type: "không phải là loại lưu trữ được hỗ trợ." + storage_error: "Đã xảy ra lỗi với kết nối lưu trữ." + invalid_input: "Đầu vào không hợp lệ." activity: item: created_by_on: "được tạo bởi %{user} vào %{datetime}" - created_by_on_time_entry: "thời gian ghi bởi %{user} vào %{datetime}" + created_by_on_time_entry: "thời gian được %{user} ghi lại vào %{datetime}" created_on: "được tạo vào %{datetime}" - created_on_time_entry: "thời gian ghi vào %{datetime}" + created_on_time_entry: "thời gian đăng nhập %{datetime}" updated_by_on: "được cập nhật bởi %{user} vào %{datetime}" - updated_by_on_time_entry: "thời gian ghi được cập nhật bởi %{user} vào %{datetime}" - updated_on: "được cập nhật vào %{datetime}" - updated_on_time_entry: "thời gian ghi được cập nhật vào %{datetime}" - deleted_on: "được xóa vào %{datetime}" - deleted_by_on: "được xóa bởi %{user} vào %{datetime}" - added_on: "được thêm vào %{datetime}" + updated_by_on_time_entry: "thời gian đã ghi được cập nhật bởi %{user} vào %{datetime}" + updated_on: "đã cập nhật vào %{datetime}" + updated_on_time_entry: "thời gian đã đăng nhập được cập nhật vào %{datetime}" + deleted_on: "đã xóa trên %{datetime}" + deleted_by_on: "đã bị xóa bởi %{user} vào %{datetime}" + added_on: "đã thêm vào %{datetime}" added_by_on: "được thêm bởi %{user} vào %{datetime}" - removed_on: "được gỡ bỏ vào %{datetime}" - removed_by_on: "được gỡ bỏ bởi %{user} vào %{datetime}" - parent_without_of: "Dự án con" - parent_no_longer: "Không còn là dự án con của" + removed_on: "đã xóa vào %{datetime}" + removed_by_on: "bị xóa bởi %{user} trên %{datetime}" + parent_without_of: "dự án con" + parent_no_longer: "Không còn tiểu dự án của" time_entry: hour: other: "%{count} giờ" hour_html: other: "%{count} giờ" - updated: "thay đổi từ %{old_value} thành %{value}" - logged_for: "Ghi lại cho" + updated: "đã thay đổi từ %{old_value} thành %{value}" + logged_for: "Đã đăng nhập" filter: - changeset: "Nhóm thay đổi" - message: "Diễn đàn" - news: "Tin tức" + changeset: "Bộ thay đổi" + message: "diễn đàn" + news: "tin tức" project_details: "Chi tiết dự án" - subproject: "Bao gồm dự án con" - time_entry: "Thời gian" - wiki_edit: "Wiki" + subproject: "Bao gồm các tiểu dự án" + time_entry: "dành thời gian" + wiki_edit: "wiki" work_package: "Work Packages" project_phase: - activated: "đã kích hoạt" - added_date: "set to %{date}" - changed_date: "changed from %{from} to %{to}" - deactivated: "đã tắt" - deleted_project_phase: "Deleted project phase" - phase_and_both_gates: "%{phase_message}. %{start_gate_message}, and %{finish_gate_message}" + activated: "kích hoạt" + added_date: "đặt thành %{date}" + changed_date: "đã thay đổi từ %{from} thành %{to}" + deactivated: "bị vô hiệu hóa" + deleted_project_phase: "Giai đoạn dự án đã xóa" + phase_and_both_gates: "%{phase_message}. %{start_gate_message} và %{finish_gate_message}" phase_and_one_gate: "%{phase_message}. %{gate_message}" - removed_date: "date deleted %{date}" + removed_date: "ngày xóa %{date}" #common attributes of all models attributes: active: "Đang hoạt động" assigned_to: "Người được giao" - assignee: "Người được giao" + assignee: "Người được chuyển nhượng" attachments: "Đính kèm" - actor: "Actor" - action: "Hành động" + actor: "tác nhân" + action: "hành động" api_key: "Khóa API" author: "Tác giả" - avatar: "Ảnh đại diện" + avatar: "hình đại diện" base: "Lỗi tổng quan:" - body: "Body" + body: "cơ thể" blocks_ids: "ID của các work package bị chặn" - category: "Thể loại" + category: "thể loại" comment: "Nhận xét" - comments: "Nhận xét" + comments: "bình luận" content: "Nội dung" - color: "Màu sắc" - creator: "Người tạo" + color: "màu sắc" + creator: "Người sáng tạo" created_at: "Tạo ngày" - custom_field: "Tùy chỉnh mục" + custom_field: "Trường tùy chỉnh" custom_options: "Giá trị có thể" custom_values: "Tùy chỉnh mục" - date: "Ngày" - dates_interval: "Khoảng thời gian" + date: "ngày" + dates_interval: "Phạm vi ngày" default_columns: "Cột mặc định" description: "Mô tả" - derived_due_date: "Ngày kết thúc suy ra" - derived_estimated_hours: "Tổng thời gian làm việc" - derived_start_date: "Ngày bắt đầu suy ra" - direction: "Direction" + derived_due_date: "Ngày kết thúc phái sinh" + derived_estimated_hours: "Tổng công việc" + derived_start_date: "Ngày bắt đầu có nguồn gốc" + direction: "Hướng" display_sums: "Hiển thị tổng" - domain: "Miền" + domain: "miền" due_date: "Ngày kết thúc" - estimated_hours: "Công việc" - estimated_time: "Công việc" - email: "Thư điện tử" - entity_type: "Entity" - expires_at: "Expires on" + estimated_hours: "làm việc" + estimated_time: "làm việc" + email: "email" + entity_type: "thực thể" + expires_at: "Hết hạn vào" firstname: "Tên" - filter: "Bộ lọc" - group: "Nhóm" - groups: "Các Nhóm" - hexcode: "Mã Hex" + filter: "bộ lọc" + group: "nhóm" + groups: "nhóm" + hexcode: "Mã hex" id: "ID" is_default: "Giá trị mặc định" is_for_all: "Cho tất cả các dự án" - public: "Công cộng" - principal: "User or group" + public: "công khai" + principal: "Người dùng hoặc nhóm" #kept for backwards compatibility issue: "Work Package" - journal: "Nhật ký" - journal_notes: "Nhận xét" + journal: "nhật ký" + journal_notes: "bình luận" lastname: "Họ" login: "Tên người dùng" - lock_version: "Khóa phiên bản" + lock_version: "Phiên bản khóa" mail: "Thư điện tử" name: "Tên" - note: "Ghi chú" + note: "Lưu ý" notes: "Ghi chú" - number: "Number" - options: "Tuỳ chọn" + number: "con số" + options: "tùy chọn" operator: "Toán tử" password: "Mật khẩu" - priority: "Độ ưu tiên" - project: "Dự án" - project_ids: "Project IDs" - project_phase: "Project phase" - project_phase_definition: "Project phase" - reason: "Lý do" + priority: "ưu tiên" + project: "dự án" + project_ids: "ID dự án" + project_phase: "Giai đoạn dự án" + project_phase_definition: "Giai đoạn dự án" + reason: "lý do" responsible: "Trách nhiệm" - required: "Bắt buộc" - recipient: "Recipient" + required: "bắt buộc" + recipient: "Người nhận" role: "Vai trò" roles: "Vai trò" - search: "Tìm kiếm" + search: "tìm kiếm" start_date: "Ngày bắt đầu" status: "Trạng thái" - state: "State" + state: "tiểu bang" subject: "Chủ đề" - slug: "Slug" + slug: "sên" summary: "Tóm tắt" - template: "Template" + template: "mẫu" time_zone: "Múi giờ" - text: "Văn bản" + text: "văn bản" title: "Tiêu đề" - type: "Kiểu" - typeahead: "Autocomplete" - uid: "Unique identifier" + type: "loại" + typeahead: "tự động hoàn thành" + uid: "Mã định danh duy nhất" updated_at: "Được cập nhật vào lúc" - updated_on: "Được cập nhật vào lúc" + updated_on: "Đã cập nhật vào" uploader: "Tải lên" - user: "Người dùng" - username: "Tên người dùng" + user: "người dùng" + username: "tên người dùng" unit: "Đơn vị" - value: "Giá trị" - values: "Values" + value: "giá trị" + values: "giá trị" version: "Phiên bản" - visible: "Hiển thị" + visible: "có thể nhìn thấy" work_package: "Work Package" - work_package_id: "Work Package" + work_package_id: "Gói công việc" backup: - failed: "Sao lưu thất bại" + failed: "Sao lưu không thành công" label_backup_token: "Sao lưu token" label_create_token: "Tạo bản sao lưu token" label_delete_token: "Xóa bản sao lưu token" @@ -2101,8 +2099,8 @@ vi: reset_token: action_create: Tạo mới action_reset: Reset - heading_reset: "Tạo lại bản sao lưu token" - heading_create: "Tạo bản sao lưu token" + heading_reset: "Đặt lại mã thông báo dự phòng" + heading_create: "Tạo mã thông báo dự phòng" implications: > Kích hoạt sao lưu sẽ cho phép bất kỳ người dùng nào có quyền và token sao lưu đều có thể tải xuống bản sao lưu có chứa tất cả dữ liệu của trang này. Nó cũng bao gồm dữ liệu của tất cả người dùng khác. info: > @@ -2119,12 +2117,12 @@ vi: token_cooldown: Mã token sao lưu sẽ có giá trị trong %{hours} giờ. backup_pending: Có một bản sao lưu đang chờ xử lý. limit_reached: Bạn chỉ có thể tạo %{limit} bản sao lưu mỗi ngày. - button_actions: "Hành động" + button_actions: "hành động" button_add: "Thêm" - button_add_comment: "Thêm bình luận" - button_add_item_above: "Add item above" - button_add_item_below: "Add item below" - button_add_sub_item: "Add sub-item" + button_add_comment: "Thêm nhận xét" + button_add_item_above: "Thêm mục ở trên" + button_add_item_below: "Thêm mục bên dưới" + button_add_sub_item: "Thêm mục phụ" button_add_member: Thêm thành viên button_add_watcher: "Thêm người theo dõi" button_annotate: "Chú giải" @@ -2141,30 +2139,30 @@ vi: button_click_to_reveal: "Chọn để xem" button_close: "Đóng" button_collapse_all: "Thu gọn tất cả" - button_confirm: "Confirm" + button_confirm: "Xác nhận" button_configure: "Thiết lập" button_continue: "Tiếp tục" - button_complete: "Complete" + button_complete: "Hoàn thành" button_copy: "Sao chép" - button_copy_to_clipboard: "Sao chép vào clipboard" + button_copy_to_clipboard: "Sao chép vào khay nhớ tạm" button_copy_link_to_clipboard: "Sao chép liên kết vào clipboard" button_create: "Tạo mới" button_create_and_continue: "Tạo và tiếp tục" button_decline: "Từ chối" button_delete: "Xoá" - button_delete_permanently: "Delete permanently" + button_delete_permanently: "Xóa vĩnh viễn" button_delete_watcher: "Xóa watcher %{name}" button_download: "Tải" - button_disable: "Disable" + button_disable: "vô hiệu hóa" button_duplicate: "Nhân đôi" - button_duplicate_and_follow: "Duplicate and follow" + button_duplicate_and_follow: "Nhân bản và làm theo" button_edit: "Chỉnh sửa" - button_enable: "Enable" - button_edit_associated_wikipage: "Edit associated wiki page: %{page_title}" + button_enable: "Kích hoạt" + button_edit_associated_wikipage: "Chỉnh sửa trang wiki liên quan: %{page_title}" button_expand_all: "Mở rộng tất cả" - button_favorite: "Thêm vào yêu thích" - button_filter: "Bộ lọc" - button_finish_setup: "Finish setup" + button_favorite: "Thêm vào mục yêu thích" + button_filter: "bộ lọc" + button_finish_setup: "Hoàn tất thiết lập" button_generate: "Tạo" button_list: "Danh sách" button_lock: "Khóa" @@ -2174,8 +2172,8 @@ vi: button_print: "In" button_quote: "Trích dẫn" button_remove: Xoá - button_remove_permanently: "Remove permanently" - button_remove_reminder: "Remove reminder" + button_remove_permanently: "Xóa vĩnh viễn" + button_remove_reminder: "Xóa lời nhắc" button_rename: "Đổi tên" button_replace: "Thay thế" button_revoke: "Thu hồi" @@ -2185,8 +2183,8 @@ vi: button_save: "Lưu" button_save_as: "Lưu dưới dạng" button_save_back: "Lưu và trở lại" - button_select: "Select" - button_set_reminder: "Set reminder" + button_select: "chọn" + button_set_reminder: "Đặt lời nhắc" button_show: "Hiện" button_sort: "Sắp xếp" button_submit: "Gửi" @@ -2194,7 +2192,7 @@ vi: button_unarchive: "Ngừng lưu trữ" button_uncheck_all: "Bỏ chọn tất cả" button_unlock: "Mở khoá" - button_unfavorite: "Gỡ bỏ khỏi yêu thích" + button_unfavorite: "Xóa khỏi mục yêu thích" button_unwatch: "Ngừng theo dõi" button_update: "Cập Nhật" button_upgrade: "Nâng cấp" @@ -2206,28 +2204,28 @@ vi: button_add_menu_entry: "Thêm mục trình đơn" button_configure_menu_entry: "Cấu hình các mục trình đơn" button_delete_menu_entry: "Xóa mục trình đơn" - button_view_shared_work_packages: "Xem các gói công việc chia sẻ" + button_view_shared_work_packages: "Xem các gói công việc được chia sẻ" button_manage_roles: "Quản lý vai trò" button_remove_member: "Xóa thành viên" - button_remove_member_and_shares: "Xóa thành viên và quyền chia sẻ" + button_remove_member_and_shares: "Xóa thành viên và chia sẻ" button_revoke_work_package_shares: "Thu hồi chia sẻ gói công việc" button_revoke_access: "Thu hồi quyền truy cập" button_revoke_all: "Thu hồi tất cả" button_revoke_only: "Chỉ thu hồi %{shared_role_name}" button_publish: "Công khai" - button_unpublish: "Riêng tư" + button_unpublish: "Đặt ở chế độ riêng tư" consent: checkbox_label: Tôi đã ghi nhận và đồng ý với những điều trên. failure_message: Sự đồng ý không thành công, không thể tiếp tục. title: Sự đồng ý của người dùng decline_warning_message: Bạn đã từ chối đồng ý và đã đăng xuất. - user_has_consented: The user gave their consent to your [configured consent information text](consent_settings). - not_yet_consented: The user has not yet given their consent to your [configured consent information text](consent_settings). They will be reminded the next time they log in. - contact_mail_instructions: Xác định địa chỉ email mà người dùng có thể liên hệ với người quản lý dữ liệu để thực hiện yêu cầu thay đổi hoặc xóa dữ liệu. + user_has_consented: Người dùng đã đồng ý với [configured consent information text](consent_settings) của bạn. + not_yet_consented: Người dùng chưa đồng ý với [configured consent information text](consent_settings) của bạn. Họ sẽ được nhắc nhở vào lần đăng nhập tiếp theo. + contact_mail_instructions: Xác định địa chỉ thư mà người dùng có thể liên hệ với bộ điều khiển dữ liệu để thực hiện các yêu cầu thay đổi hoặc xóa dữ liệu. contact_your_administrator: Vui lòng liên hệ với quản trị viên của bạn nếu bạn muốn xóa tài khoản của mình. - contact_this_mail_address: Vui lòng liên hệ với %{mail_address} nếu bạn muốn xóa tài khoản của mình. - text_update_consent_time: Đánh dấu ô này để buộc người dùng phải đồng ý lại. Kích hoạt khi bạn đã thay đổi khía cạnh pháp lý của thông tin đồng ý trên. - update_consent_last_time: "Cập nhật đồng ý lần cuối: %{update_time}" + contact_this_mail_address: Vui lòng liên hệ %{mail_address} nếu bạn muốn xóa tài khoản của mình. + text_update_consent_time: Chọn hộp này để buộc người dùng đồng ý lại. Bật khi bạn đã thay đổi khía cạnh pháp lý của thông tin chấp thuận ở trên. + update_consent_last_time: "Lần cập nhật cuối cùng về sự đồng ý: %{update_time}" copy_project: title: 'Sao chép dự án "%{source_project_name}"' started: 'Bắt đầu sao chép dự án "%{source_project_name}" sang "%{target_project_name}". Bạn sẽ được thông báo bằng thư ngay sau khi "%{target_project_name}" sẵn sàng.' @@ -2235,7 +2233,7 @@ vi: failed_internal: "Sao chép không thành công do lỗi nội bộ." succeeded: "Lập dự án %{target_project_name}" errors: "Lỗi" - project_custom_fields: "Trường tùy chỉnh trên dự án" + project_custom_fields: "Các trường tùy chỉnh trên dự án" x_objects_of_this_type: zero: "Không có đối tượng thuộc loại này" one: "Một đối tượng của loại này" @@ -2243,31 +2241,31 @@ vi: text: failed: 'Không thể chép dự án "%{source_project_name}" sang dự án "%{target_project_name}".' succeeded: 'Đã sao chép dự án "%{source_project_name}" sang "%{target_project_name}".' - source_project_label: "Project copied" + source_project_label: "Đã sao chép dự án" copy_options: - dependencies_label: "Copy from project" + dependencies_label: "Sao chép từ dự án" create_project: - attributes_heading: "Fill in this mandatory information to work on your projects." + attributes_heading: "Điền thông tin bắt buộc này để thực hiện các dự án của bạn." template_label: "Sử dụng mẫu" - template_heading: "Select a project template to work with the most common project management methods, or create a project from scratch." + template_heading: "Chọn mẫu dự án để làm việc với các phương pháp quản lý dự án phổ biến nhất hoặc tạo dự án từ đầu." copy_options: - dependencies_label: "Copy from template" + dependencies_label: "Sao chép từ mẫu" blank_template: - label: "Blank project" - description: Start from scratch. Manually add project attributes, members and modules. - blank_description: No description provided. + label: "Dự án trống" + description: Bắt đầu lại từ đầu. Thêm thủ công các thuộc tính, thành viên và mô-đun của dự án. + blank_description: Không có mô tả được cung cấp. create_portfolio: - template_heading: "Select a portfolio template to work with the most common project management methods, or create a portfolio from scratch." + template_heading: "Chọn mẫu danh mục đầu tư để làm việc với các phương pháp quản lý dự án phổ biến nhất hoặc tạo danh mục đầu tư từ đầu." blank_template: - label: "Blank portfolio" - description: Start from scratch. Manually add portfolio attributes, members and modules. + label: "Danh mục trống" + description: Bắt đầu lại từ đầu. Thêm thủ công các thuộc tính danh mục đầu tư, thành viên và mô-đun. create_program: - template_heading: "Select a program template to work with the most common project management methods, or create a program from scratch." + template_heading: "Chọn mẫu chương trình để làm việc với các phương pháp quản lý dự án phổ biến nhất hoặc tạo chương trình từ đầu." blank_template: - label: "Blank program" - description: Start from scratch. Manually add program attributes, members and modules. - create_wiki_page: "Create new wiki page" - create_wiki_page_button: "Trang wiki" + label: "Chương trình trống" + description: Bắt đầu lại từ đầu. Thêm thủ công các thuộc tính chương trình, thành viên và mô-đun. + create_wiki_page: "Tạo trang wiki mới" + create_wiki_page_button: "trang Wiki" date: abbr_day_names: - "Chủ nhật" @@ -2291,7 +2289,7 @@ vi: - "Thg 10" - "Thg 11" - "Thg 12" - abbr_week: "Tuần" + abbr_week: "tuần" day_names: - "Chủ Nhật" - "Thứ hai" @@ -2304,24 +2302,24 @@ vi: #Use the strftime parameters for formats. #When no format has been given, it uses default. #You can provide other formats here if you like! - default: "%d-%m-%Y" + default: "%m/%d/%Y" long: "%d %B, %Y" short: "%b %d" #Don't forget the nil at the beginning; there's no such thing as a 0th month month_names: #Used in date_select and datetime_select. - null - - "Tháng Một" - - "Tháng hai" - - "Tháng Ba" - - "Tháng Tư" - - "Thg 5" - - "Tháng sáu" - - "Tháng Bảy" - - "Tháng Tám" - - "Tháng Chín" - - "Tháng mười" - - "Tháng Mười một" - - "Tháng mười hai" + - "tháng Giêng" + - "tháng hai" + - "tháng ba" + - "tháng tư" + - "tháng 5" + - "tháng sáu" + - "tháng bảy" + - "tháng tám" + - "tháng chín" + - "tháng mười" + - "tháng mười một" + - "tháng mười hai" order: - ':năm' - ':tháng' @@ -2340,9 +2338,9 @@ vi: less_than_x_minutes: other: "ít hơn %{count} phút" less_than_x_seconds: - other: "chưa tới %{count} giây" + other: "ít hơn %{count} giây" over_x_years: - other: "hơn %{count} năm" + other: "trên %{count} năm" x_days: other: "%{count} ngày" x_minutes: @@ -2365,7 +2363,7 @@ vi: other: "%{count} giây" units: minute_abbreviated: - other: "mins" + other: "phút" hour: other: "hours" day: @@ -2373,7 +2371,7 @@ vi: description_active: "Đang hoạt động?" description_attachment_toggle: "Hiển thị/ẩn các đính kèm" description_autocomplete: > - Trường này sử dụng tính năng tự động hoàn tất. Khi bạn gõ tiêu đề của một gói công việc, bạn sẽ nhận được danh sách các ứng viên có thể. Chọn một bằng cách sử dụng phím mũi tên lên và xuống và chọn nó bằng phím tab hoặc enter. Ngoài ra, bạn có thể nhập số gói công việc trực tiếp. + Trường này sử dụng tính năng tự động hoàn thành. Trong khi nhập tiêu đề của gói công việc, bạn sẽ nhận được danh sách các ứng viên tiềm năng. Chọn một bằng cách sử dụng phím mũi tên lên và mũi tên xuống và chọn nó bằng tab hoặc enter. Ngoài ra, bạn có thể nhập trực tiếp số gói công việc. description_available_columns: "Cột có sẵn" description_choose_project: "Các dự án" description_compare_from: "So sánh từ" @@ -2403,168 +2401,168 @@ vi: direction: ltr ee: features: - baseline_comparison: Baseline Comparisons - board_view: Advanced Boards - calculated_values: Calculated values - capture_external_links: Capture External Links - internal_comments: Internal Comments - custom_actions: Custom Actions - custom_field_hierarchies: Hierarchies - customize_life_cycle: Customize Life Cycle - date_alerts: Date Alerts - define_custom_style: Custom theme and logo - edit_attribute_groups: Edit Attribute Groups - gantt_pdf_export: Gantt PDF Export - ldap_groups: LDAP users and group sync - mcp_server: MCP Server - nextcloud_sso: Single Sign-On for Nextcloud Storage - one_drive_sharepoint_file_storage: OneDrive/SharePoint File Storage - placeholder_users: Placeholder Users - portfolio_management: Portfolio management - project_creation_wizard: Project initiation request - project_list_sharing: Project List Sharing - readonly_work_packages: Readonly Work Packages - scim_api: SCIM server API - sso_auth_providers: Single Sign-On - team_planner_view: Team Planner View - virus_scanning: Antivirus Scanning - weighted_item_lists: Weighted item lists - work_package_query_relation_columns: Work Package Query Relation Columns - work_package_sharing: Share work packages with external users - work_package_subject_generation: Work Package Subject Generation + baseline_comparison: So sánh cơ bản + board_view: Bảng nâng cao + calculated_values: Giá trị được tính toán + capture_external_links: Chụp liên kết bên ngoài + internal_comments: Bình luận nội bộ + custom_actions: Hành động tùy chỉnh + custom_field_hierarchies: hệ thống phân cấp + customize_life_cycle: Tùy chỉnh vòng đời + date_alerts: Cảnh báo ngày + define_custom_style: Chủ đề và logo tùy chỉnh + edit_attribute_groups: Chỉnh sửa nhóm thuộc tính + gantt_pdf_export: Xuất PDF Gantt + ldap_groups: Đồng bộ hóa nhóm và người dùng LDAP + mcp_server: Máy chủ MCP + nextcloud_sso: Đăng nhập một lần để lưu trữ Nextcloud + one_drive_sharepoint_file_storage: Lưu trữ tệp OneDrive/SharePoint + placeholder_users: Người dùng giữ chỗ + portfolio_management: Quản lý danh mục đầu tư + project_creation_wizard: Yêu cầu khởi tạo dự án + project_list_sharing: Chia sẻ danh sách dự án + readonly_work_packages: Gói công việc chỉ đọc + scim_api: API máy chủ SCIM + sso_auth_providers: Đăng nhập một lần + team_planner_view: Chế độ xem kế hoạch nhóm + virus_scanning: Quét virus + weighted_item_lists: Danh sách mặt hàng có trọng số + work_package_query_relation_columns: Cột quan hệ truy vấn gói công việc + work_package_sharing: Chia sẻ gói công việc với người dùng bên ngoài + work_package_subject_generation: Tạo chủ đề gói công việc upsell: buy_now_button: "Mua ngay" - plans_title: "Enterprise plans" - title: "Tiện ích mở rộng doanh nghiệp" - plan_title: "Enterprise %{plan} add-on" - plan_name: "%{plan} enterprise plan" - plan_text_html: "Available starting with the %{plan_name}." + plans_title: "Kế hoạch doanh nghiệp" + title: "Tiện ích bổ sung dành cho doanh nghiệp" + plan_title: "Tiện ích bổ sung %{plan} dành cho doanh nghiệp" + plan_name: "%{plan} kế hoạch doanh nghiệp" + plan_text_html: "Có sẵn bắt đầu với %{plan_name}." unlimited: "Không giới hạn" already_have_token: > - Already have a token? Add it using the button below to upgrade to the booked Enterprise plan. - hide_banner: "Hide this banner" + Bạn đã có mã thông báo chưa? Thêm nó bằng nút bên dưới để nâng cấp lên gói Enterprise đã đặt. + hide_banner: "Ẩn biểu ngữ này" homescreen_description: > - Enterprise plans extend the Community edition of OpenProject with additional [Enterprise add-ons](enterprise_url) and professional support, ideal for organizations running OpenProject in a mission-critical environment. - homescreen_subline: By upgrading, you will also be supporting an open source project. + Các gói doanh nghiệp mở rộng phiên bản Cộng đồng của OpenProject với [Enterprise add-ons](enterprise_url) bổ sung và hỗ trợ chuyên nghiệp, lý tưởng cho các tổ chức chạy OpenProject trong môi trường quan trọng. + homescreen_subline: Bằng cách nâng cấp, bạn cũng sẽ hỗ trợ một dự án nguồn mở. baseline_comparison: - description: Nổi bật các thay đổi đã thực hiện đối với danh sách này kể từ bất kỳ thời điểm nào trong quá khứ. + description: Đánh dấu những thay đổi được thực hiện trong danh sách này kể từ bất kỳ thời điểm nào trong quá khứ. benefits: - description: "Những lợi ích của phiên bản Enterprise tại chỗ là gì?" + description: "Lợi ích của phiên bản Enterprise tại chỗ là gì?" high_security: "Tính năng bảo mật" high_security_text: "Đăng nhập một lần (SAML, OpenID Connect, CAS), nhóm LDAP." installation: "Hỗ trợ cài đặt" - installation_text: "Các kỹ sư phần mềm có kinh nghiệm sẽ hướng dẫn bạn qua toàn bộ quá trình cài đặt và cấu hình trong hạ tầng của bạn." - premium_features: "Phần mở rộng Enterprise" - premium_features_text: "Bảng Agile, chủ đề và logo tùy chỉnh, đồ thị, quy trình làm việc thông minh với các hành động tùy chỉnh, tìm kiếm toàn văn cho các tệp đính kèm công việc và trường tùy chỉnh nhiều chọn." + installation_text: "Các kỹ sư phần mềm có kinh nghiệm sẽ hướng dẫn bạn thực hiện quá trình cài đặt và thiết lập hoàn chỉnh trong cơ sở hạ tầng của riêng bạn." + premium_features: "Tiện ích bổ sung dành cho doanh nghiệp" + premium_features_text: "Bảng linh hoạt, chủ đề và logo tùy chỉnh, đồ thị, quy trình làm việc thông minh với các hành động tùy chỉnh, tìm kiếm toàn văn cho tệp đính kèm gói công việc và các trường tùy chỉnh nhiều lựa chọn." professional_support: "Hỗ trợ chuyên nghiệp" - professional_support_text: "Nhận hỗ trợ tin cậy, tận tâm từ các kỹ sư hỗ trợ cấp cao với kiến thức chuyên môn về việc chạy OpenProject trong môi trường kinh doanh quan trọng." + professional_support_text: "Nhận được sự hỗ trợ đáng tin cậy, liên tục từ các kỹ sư hỗ trợ cấp cao có kiến ​​thức chuyên môn về việc chạy OpenProject trong các môi trường quan trọng trong kinh doanh." work_package_subject_generation: - description: "Create automatically generated subjects using referenced attributes and text." + description: "Tạo chủ đề được tạo tự động bằng cách sử dụng các thuộc tính và văn bản được tham chiếu." customize_life_cycle: - description: "Create and organize different project phases than the ones provided by PM2 project cycle planning." + description: "Tạo và tổ chức các giai đoạn dự án khác với các giai đoạn được cung cấp bởi việc lập kế hoạch chu trình dự án PM2." capture_external_links: - description: "Prevent social engineering attacks by capturing and warning about external links before users visit them." + description: "Ngăn chặn các cuộc tấn công xã hội bằng cách phát hiện và cảnh báo về các liên kết bên ngoài trước khi người dùng truy cập vào chúng." work_package_query_relation_columns: - description: "Need to see relations or child elements in the work package list?" + description: "Cần xem các mối quan hệ hoặc các phần tử con trong danh sách gói công việc?" edit_attribute_groups: - description: "Customize form configuration with these additional add-ons:" + description: "Tùy chỉnh cấu hình biểu mẫu với các tiện ích bổ sung sau:" features: - groups: "Thêm 1 nhóm thuộc tính" - rename: "Rename attribute groups" - related: "Add a table of related work packages" + groups: "Thêm nhóm thuộc tính mới" + rename: "Đổi tên nhóm thuộc tính" + related: "Thêm bảng các gói công việc liên quan" readonly_work_packages: - description: "Mark work packages as read-only for specific statuses." + description: "Đánh dấu các gói công việc là chỉ đọc đối với các trạng thái cụ thể." custom_field_hierarchies: - description: "Hierarchy custom fields allow organizing hierarchical structures in work packages and projects by making use of multi-level select lists." + description: "Các trường tùy chỉnh phân cấp cho phép tổ chức các cấu trúc phân cấp trong các gói công việc và dự án bằng cách sử dụng danh sách chọn nhiều cấp." date_alerts: - description: "Với cảnh báo ngày, bạn sẽ được thông báo về các ngày bắt đầu hoặc kết thúc sắp tới để không bao giờ bỏ lỡ hoặc quên hạn chót quan trọng." + description: "Với tính năng cảnh báo ngày, bạn sẽ được thông báo về ngày bắt đầu hoặc ngày kết thúc sắp tới để không bao giờ bỏ lỡ hoặc quên thời hạn quan trọng." weighted_item_lists: - description: "Weighted item lists allow you to create a list with underlying numeric values associated." + description: "Danh sách mục có trọng số cho phép bạn tạo danh sách có các giá trị số cơ bản được liên kết." work_package_sharing: - description: "Chia sẻ gói công việc với người dùng không phải là thành viên của dự án." + description: "Chia sẻ các gói công việc với người dùng không phải là thành viên của dự án." project_list_sharing: - description: "Share project lists with individual users." + description: "Chia sẻ danh sách dự án với người dùng cá nhân." calculated_values: - description: "Calculated values allow you to create a mathematical formula based attribute using numeric values and other project attributes and custom fields." + description: "Các giá trị được tính toán cho phép bạn tạo thuộc tính dựa trên công thức toán học bằng cách sử dụng các giá trị số cũng như các thuộc tính dự án và trường tùy chỉnh khác." define_custom_style: - title: "Custom color theme and logo" - more_info: "Lưu ý: logo sử dụng sẽ được công khai truy cập." - description: Tùy chỉnh cài đặt OpenProject của bạn với logo và màu sắc riêng. + title: "Chủ đề và logo màu tùy chỉnh" + more_info: "Lưu ý: logo được sử dụng sẽ có thể truy cập công khai." + description: Tùy chỉnh cài đặt OpenProject bằng logo và màu sắc của riêng bạn. custom_actions: - title: "Tác vụ tùy chỉnh" - description: "Hành động tùy chỉnh là các phím tắt một lần để thực hiện một tập hợp các hành động đã được định trước mà bạn có thể làm cho sẵn có trên các gói công việc nhất định dựa trên trạng thái, vai trò, loại hoặc dự án." + title: "Hành động tùy chỉnh" + description: "Hành động tùy chỉnh là các phím tắt bằng một cú nhấp chuột tới một tập hợp các hành động được xác định trước mà bạn có thể cung cấp trên các gói công việc nhất định dựa trên trạng thái, vai trò, loại hoặc dự án." mcp_server: - description: "Integrate AI agents with your OpenProject instance through MCP." + description: "Tích hợp các tác nhân AI với phiên bản OpenProject của bạn thông qua MCP." nextcloud_sso: - title: "Single Sign-On for Nextcloud Storage" - description: "Enable seamless and secure authentication for your Nextcloud storage with Single Sign-On. Simplify access management and enhance user convenience." + title: "Đăng nhập một lần để lưu trữ Nextcloud" + description: "Cho phép xác thực liền mạch và an toàn cho bộ lưu trữ Nextcloud của bạn bằng Đăng nhập một lần. Đơn giản hóa việc quản lý truy cập và nâng cao sự thuận tiện cho người dùng." scim_api: - title: "SCIM clients" - description: "Automate user management in OpenProject by seamlessly integrating external identity services like Microsoft Entra or Keycloak through our SCIM Server API. Available starting with the Enterprise corporate plan." + title: "khách hàng SCIM" + description: "Tự động hóa việc quản lý người dùng trong OpenProject bằng cách tích hợp liền mạch các dịch vụ nhận dạng bên ngoài như Microsoft Entra hoặc Keycloak thông qua API máy chủ SCIM của chúng tôi. Có sẵn bắt đầu với gói doanh nghiệp Enterprise." sso_auth_providers: - title: "Single Sign-On (SSO)" - description: "Enable users to log in via external SSO providers using SAML or OpenID Connect for seamless access and integration with existing identity systems." + title: "Đăng nhập một lần (SSO)" + description: "Cho phép người dùng đăng nhập thông qua các nhà cung cấp SSO bên ngoài bằng SAML hoặc OpenID Connect để truy cập và tích hợp liền mạch với các hệ thống nhận dạng hiện có." virus_scanning: - description: "Đảm bảo các tệp tải lên trong OpenProject được quét virus trước khi được người dùng khác truy cập." + description: "Đảm bảo các tệp được tải lên trong OpenProject được quét vi-rút trước khi người dùng khác có thể truy cập được." project_creation_wizard: - description: "Generate a step-by-step wizard to help project managers fill out a project initiation request." + description: "Tạo một trình hướng dẫn từng bước để hỗ trợ các nhà quản lý dự án điền vào đơn yêu cầu khởi tạo dự án." placeholder_users: - title: Người dùng giả định + title: Người dùng giữ chỗ description: > - Người dùng giả định là cách để gán các gói công việc cho người dùng không thuộc dự án của bạn. Chúng có thể hữu ích trong nhiều tình huống; ví dụ, nếu bạn cần theo dõi các nhiệm vụ cho một nguồn lực chưa được đặt tên hoặc chưa có sẵn, hoặc nếu bạn không muốn cấp quyền truy cập cho người đó vào OpenProject nhưng vẫn muốn theo dõi các nhiệm vụ được gán cho họ. + Người dùng giữ chỗ là một cách để gán các gói công việc cho những người dùng không thuộc dự án của bạn. Chúng có thể hữu ích trong nhiều tình huống khác nhau; ví dụ: nếu bạn cần theo dõi các nhiệm vụ cho một tài nguyên chưa được đặt tên hoặc chưa có sẵn hoặc nếu bạn không muốn cấp cho người đó quyền truy cập vào OpenProject nhưng vẫn muốn theo dõi các nhiệm vụ được giao cho họ. internal_comments: - title: Internal comments - description: "Internal comments allow an internal team to communicate amongst themselves privately. These are only visible to certain project roles and will never be visible publicly." + title: Bình luận nội bộ + description: "Nhận xét nội bộ cho phép một nhóm nội bộ liên lạc riêng tư với nhau. Những thông tin này chỉ hiển thị với một số vai trò dự án nhất định và sẽ không bao giờ hiển thị công khai." internal_comments_inline: - title: "Write internal comments only a small group can see" + title: "Viết bình luận nội bộ chỉ một nhóm nhỏ mới có thể xem" description: " " portfolio_management: - description: Align your projects to your strategic goals by organizing them into portfolios and programs. + description: Điều chỉnh các dự án của bạn theo mục tiêu chiến lược bằng cách sắp xếp chúng thành danh mục đầu tư và chương trình. teaser: title: - other: "%{count} days left of %{trial_plan} trial token" - description: "You have access to all %{trial_plan} features." + other: "%{count} ngày còn lại của %{trial_plan} mã thông báo dùng thử" + description: "Bạn có quyền truy cập vào tất cả các tính năng %{trial_plan}." trial: - not_found: "You have requested a trial token, but that request is no longer available. Please try again." - wait_for_confirmation: "We sent you an email to confirm your address in order to retrieve a trial token." + not_found: "Bạn đã yêu cầu mã thông báo dùng thử nhưng yêu cầu đó không còn nữa. Vui lòng thử lại." + wait_for_confirmation: "Chúng tôi đã gửi cho bạn một email để xác nhận địa chỉ của bạn nhằm lấy mã thông báo dùng thử." already_retrieved: > - Your trial enterprise token was already retrieved. Please check your emails for the token being attached. Please reach out to our support team if you need a new one. - successfully_saved: "Your trial enterprise token has been successfully retrieved." - token_sent: "Trial token requested" - request_again: "Request again" - resend_action: "Resend confirmation email" - welcome_title: "Quick feature overview" - welcome_description: "Nhận cái nhìn nhanh về quản lý dự án và hợp tác nhóm với phiên bản Enterprise của OpenProject." + Mã thông báo doanh nghiệp dùng thử của bạn đã được truy xuất. Vui lòng kiểm tra email của bạn để biết mã thông báo được đính kèm. Vui lòng liên hệ với nhóm hỗ trợ của chúng tôi nếu bạn cần một cái mới. + successfully_saved: "Mã thông báo doanh nghiệp dùng thử của bạn đã được truy xuất thành công." + token_sent: "Đã yêu cầu mã thông báo dùng thử" + request_again: "Yêu cầu lại" + resend_action: "Gửi lại email xác nhận" + welcome_title: "Tổng quan về tính năng nhanh" + welcome_description: "Nhận tổng quan nhanh về quản lý dự án và cộng tác nhóm với phiên bản OpenProject Enterprise." confirmation_info: > - We sent you an email on %{date} to %{email} with all the information to start the free trial of OpenProject Enterprise. Please check your inbox and click the confirmation link provided to start your 14-day free trial. + Chúng tôi đã gửi cho bạn email theo số %{date} tới %{email} kèm theo tất cả thông tin để bắt đầu dùng thử miễn phí OpenProject Enterprise. Vui lòng kiểm tra hộp thư đến của bạn và nhấp vào liên kết xác nhận được cung cấp để bắt đầu dùng thử miễn phí 14 ngày. confirmation_subline: > - Please, check your inbox and follow the steps to start your 14-day free trial. - domain_caption: The token will be valid for your currently configured host name. + Vui lòng kiểm tra hộp thư đến của bạn và làm theo các bước để bắt đầu dùng thử miễn phí 14 ngày. + domain_caption: Mã thông báo sẽ hợp lệ cho tên máy chủ hiện được định cấu hình của bạn. receive_newsletter_html: > - I want to receive the OpenProject newsletter. + Tôi muốn nhận bản tin OpenProject . consent_html: > - I agree with the terms of service and the privacy policy. + Tôi đồng ý với điều khoản dịch vụchính sách quyền riêng tư. email_calendar_updates: state: - disabled: "Disabled." - enabled: "Enabled." + disabled: "Tàn tật." + enabled: "Đã bật." button: - disabled: "Enable" - enabled: "Disable" + disabled: "Kích hoạt" + enabled: "vô hiệu hóa" enumeration_activities: "Theo dõi hoạt động" enumeration_work_package_priorities: "Độ ưu tiên của work package" - enumeration_reported_project_statuses: "Reported status" - enumeration_caption_order_changed: "Order successfully changed." - enumeration_could_not_be_moved: "Enumeration could not be moved." + enumeration_reported_project_statuses: "Trạng thái được báo cáo" + enumeration_caption_order_changed: "Đơn hàng đã được thay đổi thành công." + enumeration_could_not_be_moved: "Việc liệt kê không thể di chuyển được." enterprise_trials: dialog_component: - title: Enterprise Trial + title: Bản dùng thử doanh nghiệp error_auth_source_sso_failed: "Đăng nhập một lần (SSO) cho người dùng '%{value}' không thành công" error_can_not_archive_project: "Dự án này không thể lưu trữ: %{errors}" error_can_not_delete_entry: "Không thể xoá" error_can_not_delete_custom_field: "Không thể xóa mục tùy biến" - error_can_not_delete_in_use_archived_undisclosed: "Cũng có các gói công việc trong các dự án đã lưu trữ. Bạn cần yêu cầu quản trị viên thực hiện việc xóa để xem các dự án bị ảnh hưởng." - error_can_not_delete_in_use_archived_work_packages: "Cũng có các gói công việc trong các dự án đã lưu trữ. Bạn cần kích hoạt lại các dự án sau trước khi thay đổi thuộc tính của các gói công việc tương ứng: %{archived_projects_urls}" + error_can_not_delete_in_use_archived_undisclosed: "Ngoài ra còn có các gói công việc trong các dự án lưu trữ. Bạn cần yêu cầu quản trị viên thực hiện việc xóa để xem dự án nào bị ảnh hưởng." + error_can_not_delete_in_use_archived_work_packages: "Ngoài ra còn có các gói công việc trong các dự án lưu trữ. Trước tiên, bạn cần kích hoạt lại các dự án sau trước khi có thể thay đổi thuộc tính của các gói công việc tương ứng: %{archived_projects_urls}" error_can_not_delete_type: explanation: 'Loại này chứa các gói công việc và không thể bị xóa. Bạn có thể xem tất cả các gói công việc bị ảnh hưởng trong cái nhìn này.' error_can_not_delete_standard_type: "Kiểu chuẩn không thể bị xóa." @@ -2575,35 +2573,35 @@ vi: error_can_not_unarchive_project: "Dự án này không thể lưu trữ: %{errors}" error_check_user_and_role: "Hãy chọn người dùng và một vai trò." error_code: "Lỗi %{code}" - error_color_could_not_be_saved: "Màu sắc không thể được lưu" - error_cookie_missing: "Cookie OpenProject bị thiếu. Vui lòng đảm bảo rằng cookies được bật, vì ứng dụng này sẽ không hoạt động đúng nếu không có cookies." + error_color_could_not_be_saved: "Không thể lưu màu" + error_cookie_missing: "Cookie OpenProject bị thiếu. Hãy đảm bảo rằng cookie đã được bật vì ứng dụng này sẽ không hoạt động bình thường nếu không có cookie." error_custom_option_not_found: "Tùy chọn không tồn tại." - error_enterprise_plan_needed: "You need the %{plan} enterprise plan to perform this action." + error_enterprise_plan_needed: "Bạn cần có gói doanh nghiệp %{plan} để thực hiện hành động này." error_enterprise_activation_user_limit: "Không thể kích hoạt tài khoản của bạn (đã đạt đến giới hạn người dùng). Vui lòng liên hệ với quản trị viên của bạn để có quyền truy cập." - error_enterprise_token_invalid_domain: "Phiên bản Enterprise không hoạt động. Miền của token Enterprise của bạn (%{actual}) không khớp với tên máy chủ của hệ thống (%{expected})." + error_enterprise_token_invalid_domain: "Phiên bản Enterprise không hoạt động. Miền mã thông báo Doanh nghiệp của bạn (%{actual}) không khớp với tên máy chủ của hệ thống (%{expected})." error_failed_to_delete_entry: "Không xóa được mục này." error_in_dependent: "Lỗi khi cố gắng thay đổi đối tượng phụ thuộc: %{dependent_class} #%{related_id} - %{related_subject}: %{error}" - error_in_new_dependent: "Lỗi khi cố gắng tạo đối tượng phụ thuộc: %{dependent_class} - %{related_subject}: %{error}" + error_in_new_dependent: "Lỗi khi tạo đối tượng phụ thuộc: %{dependent_class} - %{related_subject}: %{error}" error_invalid_selected_value: "Giá trị đã chọn không hợp lệ." - error_journal_attribute_not_present: "Nhật ký không chứa thuộc tính %{attribute}." - error_pdf_export_too_many_columns: "Quá nhiều cột được chọn cho xuất PDF. Vui lòng giảm số lượng cột." - error_pdf_date_range_too_long: "The selected work package date range exceeds the allowable PDF export limit. Please condense the range to a maximum of %{years} years." - error_pdf_failed_to_export: "Xuất PDF không thể được lưu: %{error}" - error_token_authenticity: "Không thể xác minh token Cross-Site Request Forgery. Bạn có cố gắng gửi dữ liệu trên nhiều trình duyệt hoặc tab không? Vui lòng đóng tất cả các tab và thử lại." - error_reminder_not_found: "The reminder was not found or was already notified about." + error_journal_attribute_not_present: "Tạp chí không chứa thuộc tính %{attribute}." + error_pdf_export_too_many_columns: "Đã chọn quá nhiều cột để xuất PDF. Vui lòng giảm số lượng cột." + error_pdf_date_range_too_long: "Phạm vi ngày của gói công việc đã chọn vượt quá giới hạn xuất PDF cho phép. Vui lòng thu gọn phạm vi tối đa %{years} năm." + error_pdf_failed_to_export: "Không thể lưu bản xuất PDF: %{error}" + error_token_authenticity: "Không thể xác minh mã thông báo giả mạo yêu cầu trên nhiều trang web. Bạn đã thử gửi dữ liệu trên nhiều trình duyệt hoặc tab chưa? Vui lòng đóng tất cả các tab và thử lại." + error_reminder_not_found: "Không tìm thấy lời nhắc hoặc đã được thông báo về nó." error_work_package_not_found_in_project: "Work package không được tìm thấy hoặc không thuộc về dự án này" - error_work_package_id_not_found: "Không tìm thấy gói công việc." + error_work_package_id_not_found: "Gói công việc không được tìm thấy." error_must_be_project_member: "phải là thành viên dự án" - error_migrations_are_pending: "Cài đặt OpenProject của bạn có các di trú cơ sở dữ liệu đang chờ xử lý. Có thể bạn đã bỏ lỡ việc chạy các di trú trong lần nâng cấp gần nhất. Vui lòng kiểm tra hướng dẫn nâng cấp để nâng cấp cài đặt của bạn đúng cách." - error_migrations_visit_upgrade_guides: "Vui lòng truy cập hướng dẫn nâng cấp của chúng tôi" + error_migrations_are_pending: "Bản cài đặt OpenProject của bạn có quá trình di chuyển cơ sở dữ liệu đang chờ xử lý. Bạn có thể đã bỏ lỡ quá trình di chuyển trong lần nâng cấp gần đây nhất của mình. Vui lòng kiểm tra hướng dẫn nâng cấp để nâng cấp đúng cách cài đặt của bạn." + error_migrations_visit_upgrade_guides: "Vui lòng truy cập tài liệu hướng dẫn nâng cấp của chúng tôi" error_no_default_work_package_status: 'Không có tình trạng mặc định được định nghĩa cho work package. Vui lòng kiểm tra cấu hình của bạn (đi đến "Quản lý-> các trạng thái của work package").' error_no_type_in_project: "Không có kiểu được liên kết với dự án này. Hãy kiểm tra lại phần \"Thiết Lập Dự Án\"." error_omniauth_registration_timed_out: "Đăng ký thông qua một nhà cung cấp bên ngoài xác thực hết thời gian. Xin vui lòng thử lại." - error_omniauth_invalid_auth: "Thông tin xác thực trả về từ nhà cung cấp danh tính không hợp lệ. Vui lòng liên hệ với quản trị viên của bạn để được trợ giúp thêm." + error_omniauth_invalid_auth: "Thông tin xác thực được trả về từ nhà cung cấp danh tính không hợp lệ. Vui lòng liên hệ với quản trị viên của bạn để được trợ giúp thêm." error_password_change_failed: "Đã xảy ra lỗi khi cố gắng thay đổi mật khẩu." error_scm_command_failed: "Lỗi xảy ra khi truy cập vào kho lưu trữ: %{value}" error_scm_not_found: "Mục hay revision không được tìm thấy trong kho." - error_type_could_not_be_saved: "Loại không thể được lưu" + error_type_could_not_be_saved: "Không thể lưu loại" error_unable_delete_status: "Trạng thái work package không thể bị xóa vì nó được sử dụng bởi ít nhất 1 work package." error_unable_delete_default_status: "Không thể xoá tình trạng mặc định của work package. Vui lòng chọn tình trạng work package khác trước khi xóa một trong hiện tại." error_unable_to_connect: "Không thể kết nối (%{value})" @@ -2614,16 +2612,16 @@ vi: error_menu_item_not_created: Không thể thêm mục trình đơn error_menu_item_not_saved: Không thể lưu mục trình đơn error_wiki_root_menu_item_conflict: > - Không thể đổi tên "%{old_name}" thành "%{new_name}" do có xung đột giữa mục menu kết quả với mục menu hiện tại "%{existing_caption}" (%{existing_identifier}). - error_external_authentication_failed_message: "An error occurred during external authentication: %{message}" - error_attribute_not_highlightable: "Thuộc tính(s) không thể tô sáng: %{attributes}" + Không thể đổi tên "%{old_name}" thành "%{new_name}" do xung đột giữa mục menu kết quả với mục menu hiện có "%{existing_caption}" (%{existing_identifier}). + error_external_authentication_failed_message: "Đã xảy ra lỗi trong quá trình xác thực bên ngoài: %{message}" + error_attribute_not_highlightable: "(Các) thuộc tính không thể đánh dấu: %{attributes}" events: changeset: "Đã chỉnh sửa Changeset (bộ thay đổi)" message: Đã chỉnh sửa tin nhắn - news: Tin tức - project_details: "Project details edited" - project: "Đã chỉnh sửa dự án" - projects: "Dự án đã được chỉnh sửa" + news: tin tức + project_details: "Chi tiết dự án đã được chỉnh sửa" + project: "Dự án đã chỉnh sửa" + projects: "Dự án đã chỉnh sửa" reply: Đã phản hồi time_entry: "Đã chỉnh sửa Timelog" wiki_page: "Đã chỉnh sửa trang Wiki" @@ -2632,15 +2630,15 @@ vi: work_package_note: "Đã thêm ghi chú của work package" title: project: "Dự án: %{name}" - subproject: "Dự án con: %{name}" + subproject: "Tiểu dự án: %{name}" export: dialog: - title: "Xuất" - submit: "Xuất" + title: "xuất khẩu" + submit: "xuất khẩu" save_export_settings: - label: "Save settings" + label: "Lưu cài đặt" format: - label: "Định dạng tệp tin" + label: "Định dạng tệp" options: csv: label: "CSV" @@ -2650,115 +2648,115 @@ vi: label: "XLS" columns: input_label_report: "Thêm cột vào bảng thuộc tính" - input_caption_report: "Theo mặc định, tất cả các thuộc tính được thêm vào dưới dạng cột trong danh sách gói công việc đều được chọn. Các trường văn bản dài không có trong bảng thuộc tính, nhưng có thể được hiển thị bên dưới." - input_caption_table: "Theo mặc định, tất cả các thuộc tính được thêm vào dưới dạng cột trong danh sách gói công việc đều được chọn. Các trường văn bản dài không khả dụng trong xuất dựa trên bảng." - input_caption_required: "It is not possible to export the view without any column. Please add at least one column." + input_caption_report: "Theo mặc định, tất cả các thuộc tính được thêm dưới dạng cột trong danh sách gói công việc đều được chọn. Các trường văn bản dài không có sẵn trong bảng thuộc tính nhưng có thể hiển thị bên dưới nó." + input_caption_table: "Theo mặc định, tất cả các thuộc tính được thêm dưới dạng cột trong danh sách gói công việc đều được chọn. Các trường văn bản dài không có sẵn khi xuất theo bảng." + input_caption_required: "Không thể xuất chế độ xem mà không có bất kỳ cột nào. Vui lòng thêm ít nhất một cột." pdf: export_type: label: "Loại xuất PDF" options: table: - label: "Danh sách" - caption: "Xuất danh sách các gói công việc thành một bảng có các cột mong muốn." + label: "bàn" + caption: "Xuất danh sách gói công việc trong một bảng có các cột mong muốn." report: label: "Báo cáo" - caption: "Xuất gói công việc thành báo cáo chi tiết về tất cả các gói công việc trong danh sách." + caption: "Xuất gói công việc trên một báo cáo chi tiết của tất cả các gói công việc trong danh sách." gantt: - label: "Biểu đồ Gantt" - caption: "Xuất danh sách các gói công việc theo dạng biểu đồ Gantt." + label: "biểu đồ Gantt" + caption: "Xuất danh sách gói công việc ở dạng xem sơ đồ Gantt." include_images: label: "Bao gồm hình ảnh" - caption: "Loại trừ hình ảnh để giảm kích thước của tệp PDF xuất ra." + caption: "Loại trừ hình ảnh để giảm kích thước xuất PDF." gantt_zoom_levels: - label: "Mức độ thu phóng" - caption: "Chọn mức độ thu phóng cho các ngày hiển thị trên biểu đồ." + label: "Mức thu phóng" + caption: "Chọn mức thu phóng cho các ngày được hiển thị trong biểu đồ." options: - days: "Ngày" - weeks: "Tuần" + days: "ngày" + weeks: "tuần" months: "Tháng" - quarters: "Quý" + quarters: "Khu" column_width: - label: "Chiều rộng cột bảng" + label: "Chiều rộng cột của bảng" options: - narrow: "Hẹp" - medium: "Vừa phải" - wide: "Rộng" + narrow: "Thu hẹp" + medium: "Trung bình" + wide: "rộng" very_wide: "Rất rộng" paper_size: label: "Kích thước giấy" - caption: "Tùy thuộc vào kích thước biểu đồ, có thể xuất nhiều hơn một trang." + caption: "Tùy thuộc vào kích thước biểu đồ, nhiều trang có thể được xuất." long_text_fields: input_caption: "Theo mặc định, tất cả các trường văn bản dài đều được chọn." - input_label: "Thêm các trường văn bản dài" + input_label: "Thêm trường văn bản dài" input_placeholder: "Tìm kiếm các trường văn bản dài" drag_area_label: "Quản lý các trường văn bản dài" xls: include_relations: label: "Bao gồm các mối quan hệ" - caption: "Tùy chọn này sẽ tạo bản sao của mỗi gói công việc cho mọi mối quan hệ mà nó có với một gói công việc khác." + caption: "Tùy chọn này sẽ tạo một bản sao của từng gói công việc cho mọi mối quan hệ của gói công việc này với gói công việc khác." include_descriptions: label: "Bao gồm mô tả" caption: "Tùy chọn này sẽ thêm cột mô tả ở định dạng thô." your_work_packages_export: "Các gói công việc đang được xuất khẩu" - your_projects_export: "Projects are being exported" - succeeded: "Xuất hoàn tất" + your_projects_export: "Dự án đang được xuất khẩu" + succeeded: "Đã xuất xong" failed: "Đã xảy ra lỗi khi cố gắng xuất các gói công việc: %{message}" demo: - heading: "Demo PDF" - footer: "Generated by OpenProject" - button_text: Generate Demo PDF + heading: "Bản demo PDF" + footer: "Được tạo bởi OpenProject" + button_text: Tạo bản PDF demo errors: - embedded_table_with_too_many_columns: "This embedded work package table could not fit on the page, please reduce the number of columns." + embedded_table_with_too_many_columns: "Bảng gói công việc được nhúng này không thể vừa trên trang, vui lòng giảm số lượng cột." format: atom: "Atom" csv: "CSV" pdf: "PDF" pdf_overview_table: "Bảng PDF" - pdf_report_with_images: "Báo cáo PDF với hình ảnh" + pdf_report_with_images: "Báo cáo PDF có hình ảnh" pdf_report: "Báo cáo PDF" - pdf_gantt: "Gantt PDF" + pdf_gantt: "PDF Gantt" image: omitted: "Hình ảnh không được xuất" macro: error: "Lỗi macro, %{message}" - attribute_not_found: "thuộc tính không tìm thấy: %{attribute}" + attribute_not_found: "không tìm thấy thuộc tính: %{attribute}" model_not_found: "mô hình thuộc tính không hợp lệ: %{model}" - resource_not_found: "tài nguyên không tìm thấy: %{resource}" - nested_rich_text_unsupported: "Nested rich text embedding currently not supported in export" + resource_not_found: "không tìm thấy tài nguyên: %{resource}" + nested_rich_text_unsupported: "Nhúng văn bản đa dạng thức lồng nhau hiện không được hỗ trợ khi xuất" units: hours: h - days: d + days: D pdf_generator: - page_nr_footer: "Page %{page} of %{total}" + page_nr_footer: "Trang %{page} trong số %{total}" template_attributes: - label: "Attributes and description" - caption: All the attributes present in the current form configuration using the default template. + label: "Thuộc tính và mô tả" + caption: Tất cả các thuộc tính có trong cấu hình biểu mẫu hiện tại sử dụng mẫu mặc định. template_contract: - label: "Contract" - caption: Work package details formatted to the standard German contract form. + label: "hợp đồng" + caption: Chi tiết gói công việc được định dạng theo mẫu hợp đồng tiêu chuẩn của Đức. dialog: - title: Generate PDF - submit: Tải + title: Tạo PDF + submit: tải về templates: - label: "Mẫu" - none_enabled: "No template has been enabled for this work package type" + label: "mẫu" + none_enabled: "Không có mẫu nào được bật cho loại gói công việc này" footer_center: - label: Footer text - caption: This text will appear on every page at the center of the footer. + label: Văn bản chân trang + caption: Văn bản này sẽ xuất hiện trên mỗi trang ở giữa chân trang. footer_right: - label: Footer text - caption: This text will appear on every page at the right of the footer. + label: Văn bản chân trang + caption: Văn bản này sẽ xuất hiện trên mỗi trang ở bên phải chân trang. hyphenation: - label: Hyphenation - caption: Break line between words for better layout and justification. + label: Dấu gạch nối + caption: Ngắt dòng giữa các từ để bố cục và căn chỉnh tốt hơn. hyphenation_language: - label: Language and hyphenation + label: Ngôn ngữ và dấu gạch nối page_orientation: - label: Page orientation - caption: Select the orientation of the pages in the PDF document. + label: Hướng trang + caption: Chọn hướng của các trang trong tài liệu PDF. options: - portrait: Portrait - landscape: Landscape + portrait: Chân dung + landscape: phong cảnh extraction: available: pdftotext: "Pdftotext có sẵn (không bắt buộc)" @@ -2769,14 +2767,14 @@ vi: tesseract: "Tesseract có (không bắt buộc)" filterable_tree_view: filter_mode: - all: "Tất cả" - label: "Filter mode" - selected: "Selected" - include_sub_items: "Include sub-items" - no_results_text: "No results" + all: "tất cả" + label: "Chế độ lọc" + selected: "đã chọn" + include_sub_items: "Bao gồm các mục phụ" + no_results_text: "Không có kết quả" toggle_switch: - label_on: "On" - label_off: "Off" + label_on: "trên" + label_off: "Tắt" general_csv_decimal_separator: "." general_csv_encoding: "UTF-8" general_csv_separator: "," @@ -2792,7 +2790,7 @@ vi: gui_validation_error_plural: "%{count} lỗi" homescreen: additional: - projects: "Các dự án mới nhất trong trường hợp này." + projects: "Các dự án có thể nhìn thấy mới nhất trong trường hợp này." no_visible_projects: "Không có không có thể nhìn thấy các dự án trong trường hợp này." users: "Người dùng đã đăng ký mới nhất trong trường hợp này." blocks: @@ -2801,79 +2799,79 @@ vi: title: "Nâng cấp lên phiên bản Enterprise" new_features: header: "Đọc về các tính năng mới và cập nhật sản phẩm." - learn_about: "Learn more about all new features" - missing: "There are no highlighted features yet." + learn_about: "Tìm hiểu thêm về tất cả các tính năng mới" + missing: "Chưa có tính năng nổi bật nào." #We need to include the version to invalidate outdated translations in other locales "17_1": new_features_title: > - The release contains various new features and improvements, such as: + Bản phát hành này bao gồm nhiều tính năng mới và cải tiến, chẳng hạn như: new_features_list: - line_0: Automated project initiation (Enterprise add-on). - line_1: "Meetings: add new or existing work packages as outcomes." - line_2: "Meetings: show participant responses in iCal subscriptions." - line_3: "Recurring meetings: duplicate agenda items to the next occurrence." - line_4: "Release to Community: Attribute highlighting." - line_5: Warning before opening external links in user-provided content (Enterprise add-on). - line_6: Improved performance and user experience, including the Activity tab and Documents module. + line_0: Khởi tạo dự án tự động (Phần mở rộng doanh nghiệp). + line_1: "Họp: Thêm các gói công việc mới hoặc hiện có làm kết quả." + line_2: "Họp: Hiển thị phản hồi của người tham gia trong các đăng ký iCal." + line_3: "Cuộc họp định kỳ: Sao chép các mục trong chương trình nghị sự sang lần họp tiếp theo." + line_4: "Phát hành cho cộng đồng: Tính năng làm nổi bật thuộc tính." + line_5: Cảnh báo trước khi mở các liên kết bên ngoài trong nội dung do người dùng cung cấp (Phần mở rộng doanh nghiệp). + line_6: Cải thiện hiệu suất và trải nghiệm người dùng, bao gồm tab Hoạt động và mô-đun Tài liệu. links: upgrade_enterprise_edition: "Nâng cấp lên phiên bản Enterprise" - postgres_migration: "Di chuyển cài đặt của bạn lên PostgreSQL" - user_guides: "Hướng dẫn người dùng" + postgres_migration: "Di chuyển cài đặt của bạn sang PostgreSQL" + user_guides: "Hướng dẫn sử dụng" faq: "Câu hỏi thường gặp" impressum: "Thông báo pháp lý" glossary: "Thuật ngữ" - shortcuts: "Phím tắt" + shortcuts: "phím tắt" blog: "OpenProject blog" - forums: "Diễn đàn cộng đồng" - security_alerts: "Security alerts" - newsletter: "Newsletter" + forums: "diễn đàn cộng đồng" + security_alerts: "Cảnh báo bảo mật" + newsletter: "Bản tin" image_conversion: - imagemagick: "Imagemagick" + imagemagick: "Phép thuật tưởng tượng" journals: changes_retracted: "Những thay đổi đã bị rút lại" caused_changes: dates_changed: "Ngày đã thay đổi" - default_attribute_written: "Thuộc tính chỉ đọc đã được ghi" - progress_mode_changed_to_status_based: "Cập nhật tính toán tiến độ" + default_attribute_written: "Thuộc tính chỉ đọc được viết" + progress_mode_changed_to_status_based: "Đã cập nhật tính toán tiến độ" status_changed: "Trạng thái '%{status_name}'" system_update: "Cập nhật hệ thống OpenProject:" - work_package_duplicate_closed: "Duplicate work package updated:" - total_percent_complete_mode_changed_to_work_weighted_average: "Calculation of % Complete totals now weighted by Work." - total_percent_complete_mode_changed_to_simple_average: "Calculation of % Complete totals now based on a simple average of only % Complete values." + work_package_duplicate_closed: "Gói công việc trùng lặp được cập nhật:" + total_percent_complete_mode_changed_to_work_weighted_average: "Tính toán % tổng số hoàn thành hiện được tính theo Công việc." + total_percent_complete_mode_changed_to_simple_average: "Tính toán tổng số % Hoàn thành hiện dựa trên mức trung bình đơn giản chỉ là các giá trị % Hoàn thành." cause_descriptions: - work_package_predecessor_changed_times: bởi các thay đổi đối với người tiền nhiệm %{link} - work_package_parent_changed_times: bởi các thay đổi đối với cha %{link} - work_package_children_changed_times: bởi các thay đổi đối với con %{link} - work_package_related_changed_times: bởi các thay đổi đối với liên quan %{link} - work_package_duplicate_closed: The status was automatically updated by the duplicated work package %{link} - unaccessable_work_package_changed: bởi các thay đổi đối với một gói công việc liên quan + work_package_predecessor_changed_times: bằng những thay đổi đối với người tiền nhiệm %{link} + work_package_parent_changed_times: bằng những thay đổi đối với cha mẹ %{link} + work_package_children_changed_times: bởi những thay đổi đối với con %{link} + work_package_related_changed_times: bởi những thay đổi đối với %{link} có liên quan + work_package_duplicate_closed: Trạng thái được cập nhật tự động bởi gói công việc trùng lặp %{link} + unaccessable_work_package_changed: bằng cách thay đổi gói công việc liên quan working_days_changed: - changed: "bởi các thay đổi đối với ngày làm việc (%{changes})" + changed: "do thay đổi ngày làm việc (%{changes})" days: - working: "%{day} hiện là ngày làm việc" - non_working: "%{day} hiện là ngày không làm việc" + working: "%{day} hiện đang hoạt động" + non_working: "%{day} hiện không hoạt động" dates: - working: "%{date} hiện là ngày làm việc" - non_working: "%{date} hiện là ngày không làm việc" - progress_mode_changed_to_status_based: Chế độ tính toán tiến độ đã được đặt thành chế độ dựa trên trạng thái - status_excluded_from_totals_set_to_false_message: hiện được bao gồm trong tổng số phân cấp - status_excluded_from_totals_set_to_true_message: hiện bị loại trừ khỏi tổng số phân cấp - status_percent_complete_changed: "% Complete changed from %{old_value}% to %{new_value}%" + working: "%{date} hiện đang hoạt động" + non_working: "%{date} hiện không hoạt động" + progress_mode_changed_to_status_based: Chế độ tính toán tiến độ được đặt thành dựa trên trạng thái + status_excluded_from_totals_set_to_false_message: hiện được bao gồm trong tổng số thứ bậc + status_excluded_from_totals_set_to_true_message: hiện đã bị loại khỏi tổng số thứ bậc + status_percent_complete_changed: "% Hoàn thành đã thay đổi từ %{old_value}% thành %{new_value}%" system_update: file_links_journal: > - Từ giờ trở đi, hoạt động liên quan đến liên kết tệp (các tệp lưu trữ bên ngoài) sẽ xuất hiện ở đây trong tab Hoạt động. Các hoạt động sau đây liên quan đến các liên kết đã tồn tại: + Từ giờ trở đi, hoạt động liên quan đến liên kết tệp (tệp được lưu trữ trong bộ nhớ ngoài) sẽ xuất hiện tại đây trong tab Hoạt động. Phần sau đây thể hiện hoạt động liên quan đến các liên kết đã tồn tại: progress_calculation_adjusted_from_disabled_mode: >- Tính toán tiến độ tự động được đặt thành chế độ dựa trên công việc và điều chỉnh với cập nhật phiên bản. progress_calculation_adjusted: >- Tính toán tiến độ tự động được điều chỉnh với cập nhật phiên bản. scheduling_mode_adjusted: >- - Scheduling mode automatically adjusted with version update. + Chế độ lập lịch tự động điều chỉnh khi cập nhật phiên bản. totals_removed_from_childless_work_packages: >- Tổng số công việc và tiến độ tự động bị xóa cho các gói công việc không có cha với cập nhật phiên bản. Đây là một nhiệm vụ bảo trì và có thể được bỏ qua một cách an toàn. total_percent_complete_mode_changed_to_work_weighted_average: >- - Child work packages without Work are ignored. + Các gói công việc con không có Công việc sẽ bị bỏ qua. total_percent_complete_mode_changed_to_simple_average: >- - Work values of child work packages are ignored. + Giá trị công việc của gói công việc con bị bỏ qua. links: configuration_guide: "Hướng dẫn cấu hình" get_in_touch: "Nếu bạn còn những câu hỏi khác ? Hãy liên lạc với chúng tôi" @@ -2882,128 +2880,128 @@ vi: instructions_after_error: "Bạn có thể thử đăng nhập lại bằng cách nhấn vào %{signin}. Nếu lỗi vẫn còn, yêu cầu quản trị của bạn để được giúp đỡ." menus: admin: - ai: "Artificial Intelligence (AI)" - aggregation: "Tổng hợp" - api_and_webhooks: "API và webhooks" + ai: "Trí tuệ nhân tạo (AI)" + aggregation: "sự tổng hợp" + api_and_webhooks: "API và webhook" mail_notification: "Thông báo qua email" mails_and_notifications: "Email và thông báo" - mcp_configurations: "Model Context Protocol (MCP)" + mcp_configurations: "Giao thức Bối cảnh Mô hình (MCP)" quick_add: - label: "Add…" + label: "Thêm vào…" my_account: access_tokens: - description: "Provider tokens are issued by OpenProject, allowing other applications to access it. Client tokens are issued by other applications, allowing OpenProject to access them." + description: "Mã thông báo của nhà cung cấp được OpenProject phát hành, cho phép các ứng dụng khác truy cập vào nó. Mã thông báo của khách hàng được phát hành bởi các ứng dụng khác, cho phép OpenProject truy cập chúng." no_results: - title: "Không có mã truy cập để hiển thị" - description: "Tất cả đã bị vô hiệu hóa. Chúng có thể được kích hoạt lại trong menu quản trị." - access_tokens: "Token truy cập" + title: "Không có mã thông báo truy cập để hiển thị" + description: "Tất cả đều đã bị vô hiệu hóa. Chúng có thể được kích hoạt lại trong menu quản trị." + access_tokens: "Mã thông báo truy cập" headers: action: "Hành động" expiration: "Hết hạn" indefinite_expiration: "Không bao giờ" - simple_revoke_confirmation: "Bạn có chắc chắn muốn thu hồi mã truy cập này không?" + simple_revoke_confirmation: "Bạn có chắc chắn muốn thu hồi mã thông báo này không?" tabs: client: - title: "Client tokens" + title: "Mã thông báo của khách hàng" provider: - title: "Provider tokens" + title: "Mã thông báo của nhà cung cấp" token/api: - blank_description: "There is no API token yet. You can create one using the button below." - blank_title: "No API token" + blank_description: "Chưa có mã thông báo API. Bạn có thể tạo một cái bằng cách sử dụng nút bên dưới." + blank_title: "Không có mã thông báo API" title: "API" - table_title: "API tokens" - text_hint: "Mã API cho phép các ứng dụng của bên thứ ba giao tiếp với phiên bản OpenProject này qua REST APIs." - static_token_name: "API token" - disabled_text: "Mã API không được kích hoạt bởi quản trị viên. Vui lòng liên hệ với quản trị viên của bạn để sử dụng tính năng này." - add_button: "API token" + table_title: "Mã thông báo API" + text_hint: "Mã thông báo API cho phép các ứng dụng của bên thứ ba giao tiếp với phiên bản OpenProject này thông qua API REST." + static_token_name: "Mã thông báo API" + disabled_text: "Mã thông báo API không được quản trị viên bật. Vui lòng liên hệ với quản trị viên của bạn để sử dụng tính năng này." + add_button: "Token API" ical: - blank_description: "To add an iCalendar token, subscribe to a new or existing calendar from within the Calendar module of a project. You must have the necessary permissions." - blank_title: "No iCalendar token" + blank_description: "Để thêm mã thông báo iCalendar, hãy đăng ký lịch mới hoặc lịch hiện có từ trong mô-đun Lịch của dự án. Bạn phải có các quyền cần thiết." + blank_title: "Không có mã thông báo iCalendar" title: "iCalendar" - table_title: "iCalendar tokens" - text_hint_link: "iCalendar tokens allow users to [subscribe to OpenProject calendars](docs_url) and view up-to-date work package information from external clients." - disabled_text: "Đăng ký iCalendar không được kích hoạt bởi quản trị viên. Vui lòng liên hệ với quản trị viên của bạn để sử dụng tính năng này." + table_title: "mã thông báo iCalendar" + text_hint_link: "Mã thông báo iCalendar cho phép người dùng [subscribe to OpenProject calendars](docs_url) và xem thông tin gói công việc cập nhật từ các máy khách bên ngoài." + disabled_text: "Quản trị viên không kích hoạt đăng ký iCalendar. Vui lòng liên hệ với quản trị viên của bạn để sử dụng tính năng này." oauth_application: - active_tokens: "Active tokens" - blank_description: "There is no third-party application access configured and active for you." - blank_title: "No OAuth application token" - last_used_at: "Last used at" + active_tokens: "Mã thông báo hoạt động" + blank_description: "Không có quyền truy cập ứng dụng của bên thứ ba nào được định cấu hình và hoạt động cho bạn." + blank_title: "Không có mã thông báo ứng dụng OAuth" + last_used_at: "Được sử dụng lần cuối vào lúc" title: "OAuth" - table_title: "OAuth application tokens" - text_hint: "OAuth application tokens allow third-party applications to connect with this OpenProject instance." + table_title: "Mã thông báo ứng dụng OAuth" + text_hint: "Mã thông báo ứng dụng OAuth cho phép ứng dụng của bên thứ ba kết nối với phiên bản OpenProject này." oauth_client: - blank_description: "There are no OAuth client tokens yet." - blank_title: "No OAuth client tokens" - failed: "An error occurred and the token couldn't be removed. Please try again later." - integration_type: "Integration type" - table_title: "OAuth client tokens" - text_hint: "OAuth client tokens allow this OpenProject instance to connect with external applications, such as file storages." + blank_description: "Chưa có mã thông báo ứng dụng khách OAuth nào." + blank_title: "Không có mã thông báo ứng dụng khách OAuth" + failed: "Đã xảy ra lỗi và không thể xóa mã thông báo. Vui lòng thử lại sau." + integration_type: "Kiểu tích hợp" + table_title: "Mã thông báo ứng dụng khách OAuth" + text_hint: "Mã thông báo ứng dụng khách OAuth cho phép phiên bản OpenProject này kết nối với các ứng dụng bên ngoài, chẳng hạn như kho lưu trữ tệp." title: "OAuth" - remove_token: "Do you really want to remove this token? You will need to login again on %{integration}." - removed: "OAuth client token successfully removed" - unknown_integration: "Unknown" + remove_token: "Bạn có thực sự muốn xóa mã thông báo này không? Bạn sẽ cần phải đăng nhập lại vào %{integration}." + removed: "Đã xóa thành công mã thông báo ứng dụng khách OAuth" + unknown_integration: "không rõ" token/rss: - add_button: "RSS token" - blank_description: "There is no RSS token yet. You can create one using the button below." - blank_title: "No RSS token" + add_button: "Token RSS" + blank_description: "Chưa có mã thông báo RSS. Bạn có thể tạo một cái bằng cách sử dụng nút bên dưới." + blank_title: "Không có mã thông báo RSS" title: "RSS" - table_title: "RSS tokens" - text_hint: "RSS tokens allow users to keep up with the latest changes in this OpenProject instance via an external RSS reader." - static_token_name: "RSS token" - disabled_text: "RSS tokens are not enabled by the administrator. Please contact your administrator to use this feature." + table_title: "Mã thông báo RSS" + text_hint: "Mã thông báo RSS cho phép người dùng cập nhật những thay đổi mới nhất trong phiên bản OpenProject này thông qua trình đọc RSS bên ngoài." + static_token_name: "mã thông báo RSS" + disabled_text: "Mã thông báo RSS không được quản trị viên kích hoạt. Vui lòng liên hệ với quản trị viên của bạn để sử dụng tính năng này." storages: - unknown_storage: "Lưu trữ không xác định" + unknown_storage: "Bộ nhớ không xác định" notifications: reasons: - assigned: "Người được giao" + assigned: "Người được chuyển nhượng" dateAlert: "Cảnh báo ngày" - mentioned: "Nhắc đến" - responsible: "Trách nhiệm" - shared: "Được chia sẻ" - watched: "Người quan sát" - reminder: "Reminder" + mentioned: "Được đề cập" + responsible: "chịu trách nhiệm" + shared: "đã chia sẻ" + watched: "người theo dõi" + reminder: "Lời nhắc" facets: unread: "Chưa đọc" unread_title: "Hiển thị chưa đọc" - all: "Tất cả" + all: "tất cả" all_title: "Hiển thị tất cả" menu: - by_project: "Chưa đọc theo dự án" - by_reason: "Lý do" + by_project: "Dự án chưa đọc" + by_reason: "lý do" inbox: "Hộp thư đến" send_notifications: "Gửi thông báo cho hành động này" work_packages: subject: created: "Gói công việc đã được tạo." - assigned: "Bạn đã được giao cho %{work_package}" - subscribed: "Bạn đã đăng ký nhận thông tin từ %{work_package}" + assigned: "Bạn đã được chỉ định vào %{work_package}" + subscribed: "Bạn đã đăng ký %{work_package}" mentioned: "Bạn đã được nhắc đến trong %{work_package}" - responsible: "Bạn đã trở thành người chịu trách nhiệm cho %{work_package}" - watched: "Bạn đang theo dõi %{work_package}" + responsible: "Bạn phải chịu trách nhiệm về %{work_package}" + watched: "Bạn đang xem %{work_package}" query: - invalid_filter: "Invalid notification filter" + invalid_filter: "Bộ lọc thông báo không hợp lệ" label_accessibility: "Trợ năng" - label_account: "Tài khoản" + label_account: "tài khoản" label_active: "Đang hoạt động" label_activate_user: "Người dùng kích họat" - label_active_in_new_projects: "Kích hoạt trong các dự án mới" - label_activity: "Hoạt động" + label_active_in_new_projects: "Tích cực trong các dự án mới" + label_activity: "hoạt động" label_add_edit_translations: "Thêm và chỉnh sửa bản dịch" label_add_another_file: "Thêm tập tin khác" label_add_columns: "Thêm cột đang chọn" label_add_note: "Thêm ghi chú" - label_add_projects: "Thêm các dự án" + label_add_projects: "Thêm dự án" label_add_related_work_packages: "Thêm work package liên quan" label_add_subtask: "Thêm subtask" label_added: "đã thêm" - label_added_by: "Thêm bởi %{author}" - label_added_by_on: "Added by %{author} on %{date}" + label_added_by: "Được thêm bởi %{author}" + label_added_by_on: "Được thêm bởi %{author} trên %{date}" label_added_time_by: "Thêm bởi %{author} cách đây %{age}" label_additional_workflow_transitions_for_assignee: "Chuyển đổi bổ sung được cho phép khi người sử dụng là người được gán" label_additional_workflow_transitions_for_author: "Các chuyển đổi bổ xung được phép khi người dùng là tác giả" label_administration: "Quản trị" - label_interface_colors: "Interface colors" - label_interface_colors_description: "These colors control how the application looks. If you modify them the theme will automatically be changed to Custom theme, but we can’t assure the compliance of the accessibility contrast minimums (WCAG 2.1). " + label_interface_colors: "Màu sắc giao diện" + label_interface_colors_description: "Những màu này kiểm soát giao diện của ứng dụng. Nếu bạn sửa đổi chúng, chủ đề sẽ tự động được thay đổi thành Chủ đề tùy chỉnh nhưng chúng tôi không thể đảm bảo việc tuân thủ mức độ tương phản tối thiểu về khả năng truy cập (WCAG 2.1)." label_age: "Tuổi" label_ago: "vài ngày trước" label_all: "tất cả" @@ -3016,67 +3014,67 @@ vi: label_api_access_key: "Khoá truy cập API" label_api_access_key_created_on: "Khoá truy cập API đựơc tạo cách đây %{value}" label_api_access_key_type: "API" - label_auto_option: "(auto)" + label_auto_option: "(tự động)" label_ical_access_key_type: "iCalendar" - label_ical_access_key_description: 'Mã iCalendar "%{token_name}" cho "%{calendar_name}" trong "%{project_name}"' - label_ical_access_key_not_present: "không có mã thông báo(s) iCalendar." - label_ical_access_key_generation_hint: "Tự động tạo khi đăng ký lịch." + label_ical_access_key_description: 'Mã thông báo iCalendar "%{token_name}" cho "%{calendar_name}" trong "%{project_name}"' + label_ical_access_key_not_present: "(Các) mã thông báo iCalendar không xuất hiện." + label_ical_access_key_generation_hint: "Tự động được tạo khi đăng ký lịch." label_ical_access_key_latest: "mới nhất" label_ical_access_key_revoke: "Thu hồi" - label_add_column: "Add column" + label_add_column: "Thêm cột" label_applied_status: "Tình trạng áp dụng" label_archive_project: "Lưu trữ dự án" label_ascending: "Tăng dần" label_assigned_to_me_work_packages: "Work package được chỉ định cho tôi" label_associated_revisions: "Các phiên bản kết hợp" - label_attachment_plural: "Đính kèm" + label_attachment_plural: "tệp đính kèm" label_attribute: "Thuộc tính" label_attribute_plural: "Thuộc tính" label_ldap_auth_source_new: "Kết nối LDAP mới" label_ldap_auth_source: "Kết nối LDAP" - label_ldap_auth_source_plural: "Các kết nối LDAP" - label_attribute_expand_text: "Văn bản đầy đủ cho '%{attribute}'" + label_ldap_auth_source_plural: "Kết nối LDAP" + label_attribute_expand_text: "Toàn bộ văn bản cho '%{attribute}'" label_authentication: "Xác thực" - label_available_custom_fields_projects: "Available custom fields projects" - label_available_global_roles: "Các vai trò toàn cầu có sẵn" - label_available_project_attributes: "Các thuộc tính dự án có sẵn" - label_available_project_forums: "Các diễn đàn dự án có sẵn" + label_available_custom_fields_projects: "Các dự án trường tùy chỉnh có sẵn" + label_available_global_roles: "Vai trò toàn cầu có sẵn" + label_available_project_attributes: "Thuộc tính dự án có sẵn" + label_available_project_forums: "Diễn đàn có sẵn" label_available_project_repositories: "Kho có sẵn" label_available_project_versions: "Phiên bản sẵn có" - label_available_project_work_package_categories: "Các loại gói công việc dự án có sẵn" - label_available_project_work_package_types: "Các loại gói công việc dự án có sẵn" - label_available_projects: "Các dự án có sẵn" + label_available_project_work_package_categories: "Danh mục gói công việc có sẵn" + label_available_project_work_package_types: "Các loại gói công việc có sẵn" + label_available_projects: "Dự án có sẵn" label_api_doc: "Tài liệu API" label_backup: "Sao lưu" label_backup_code: "Mã dự phòng" - label_basic_details: "Basic details" + label_basic_details: "Chi tiết cơ bản" label_between: "giữa" label_blocked_by: "bị chặn bởi" label_blocks: "các khối" label_blog: "Nhật ký" - label_forums_locked: "Đã khóa" + label_forums_locked: "bị khóa" label_forum_new: "Diễn đàn mới" - label_forum_plural: "Diễn đàn" - label_forum_sticky: "Chú ý" + label_forum_plural: "diễn đàn" + label_forum_sticky: "dính" label_boolean: "Boolean" - label_board_plural: "Các bảng" + label_board_plural: "bảng" label_branch: "Nhánh" label_browse: "Duyệt" - label_builtin: "Built-in" + label_builtin: "Tích hợp sẵn" label_bulk_edit_selected_work_packages: "Chỉnh sửa các work package đã chọn" label_bundled: "(Tích hợp sẵn)" label_calendar: "Lịch" label_calendars_and_dates: "Lịch và ngày" label_calendar_show: "Hiển thị Lịch" - label_category: "Thể loại" - label_completed: Đã hoàn tất + label_category: "thể loại" + label_completed: Đã hoàn thành label_consent_settings: "Sự đồng ý của người dùng" label_wiki_menu_item: Khoản mục menu wiki label_select_main_menu_item: Chọn mục trình đơn chính mới label_required_disk_storage: "Ổ đĩa lưu trữ bắt buộc" label_send_invitation: Gửi lời mời - label_calculated_value: "Calculated value" - label_change_parent: "Thay đổi cha" + label_calculated_value: "Giá trị tính toán" + label_change_parent: "Thay đổi cha mẹ" label_change_plural: "Thay đổi" label_change_properties: "Thay đổi những thuộc tính" label_change_status: "Đổi trạng thái" @@ -3100,9 +3098,9 @@ vi: label_comment_added: "Đã thêm bình luận" label_comment_delete: "Xóa bình luận" label_comment_plural: "Bình luận" - label_commits_per_author: "Cam kết theo tác giả" + label_commits_per_author: "Cam kết cho mỗi tác giả" label_commits_per_month: "Commits mỗi tháng" - label_confirmation: "Xác nhận lại mật khẩu" + label_confirmation: "xác nhận" label_contains: "bao gồm" label_content: "Nội dung" label_color_plural: "Màu sắc" @@ -3113,20 +3111,20 @@ vi: label_copy_workflow_from: "Sao chép quy trình từ" label_copy_project: "Sao chép dự án" label_core_version: "Phiên bản lõi" - label_core_build: "Xây dựng lõi" - label_created_by: "Created by %{user}" + label_core_build: "Xây dựng cốt lõi" + label_created_by: "Được tạo bởi %{user}" label_current_status: "Tình trạng hiện tại" label_current_version: "Phiên bản hiện tại" label_custom_field_add_no_type: "Thêm trường này vào một loại work package" label_custom_field_new: "Thêm mục tùy biến" - label_custom_field_plural: "Tùy chỉnh mục" + label_custom_field_plural: "Trường tùy chỉnh" label_custom_field_default_type: "Kiểu rỗng" label_custom_style: "Thiết kế" - label_custom_style_description: "Choose how OpenProject looks to you with themes, select your default colors to use in the app and how exports look like." - label_dashboard: "Bảng điều khiển" + label_custom_style_description: "Chọn cách OpenProject hiển thị với bạn bằng các chủ đề, chọn màu mặc định để sử dụng trong ứng dụng và cách xuất sẽ trông như thế nào." + label_dashboard: "Trang tổng quan" label_database_version: "Phiên bản PostgreSQL" - label_date: "Ngày" - label_dates: "Dates" + label_date: "ngày" + label_dates: "ngày tháng" label_date_and_time: "Ngày và giờ" label_date_format: "Định dạng ngày" label_date_from: "Từ" @@ -3136,25 +3134,25 @@ vi: label_default: "Mặc định" label_delete_user: "Xóa người dùng" label_delete_project: "Xóa dự án" - label_delete: "Xoá" + label_delete: "xóa" label_deleted: "đã xóa" label_deleted_custom_field: "(đã xóa các mục tùy chỉnh)" - label_deleted_custom_item: "(deleted item)" + label_deleted_custom_item: "(mục đã bị xóa)" label_deleted_custom_option: "(tùy chọn đã xóa)" - label_empty_element: "(rỗng)" - label_go_back: "Go back one menu level" - label_go_forward: "Open %{module} sub-menu" - label_missing_or_hidden_custom_option: "(giá trị thiếu hoặc không có quyền truy cập)" + label_empty_element: "(trống)" + label_go_back: "Quay lại một cấp độ menu" + label_go_forward: "Mở menu phụ %{module}" + label_missing_or_hidden_custom_option: "(thiếu giá trị hoặc thiếu quyền truy cập)" label_descending: "Giảm dần" label_details: "Chi tiết" - label_defaults: "Defaults" + label_defaults: "mặc định" label_development_roadmap: "Lộ trình phát triển" label_diff: "sự khác nhau" label_diff_inline: "nội tuyến" label_diff_side_by_side: "bên cạnh nhau" - label_digital_accessibility: "Khả năng truy cập kỹ thuật số (DE)" + label_digital_accessibility: "Khả năng tiếp cận kỹ thuật số (DE)" label_disabled: "vô hiệu hoá" - label_disabled_uppercase: "VÔ HIỆU" + label_disabled_uppercase: "bị vô hiệu hóa" label_display: "Hiển thị" label_display_per_page: "Mỗi trang: %{value}" label_display_used_statuses_only: "Chỉ hiển thị trạng thái được dùng bởi kiểu này" @@ -3165,47 +3163,47 @@ vi: label_duplicate: "Nhân đôi" label_duplicates: "Nhân đôi" label_edit: "Chỉnh sửa" - label_edit_attribute: "Edit attribute" + label_edit_attribute: "Chỉnh sửa thuộc tính" label_edit_x: "Chỉnh sửa: %{x}" label_enable_multi_select: "Bật/tắt đa lựa chọn" label_enabled_project_custom_fields: "Các trường tùy chỉnh đã bật" label_enabled_project_modules: "Các mô-đun đã bật" - label_enabled_project_activities: "Hoạt động theo dõi thời gian được bật" + label_enabled_project_activities: "Đã bật hoạt động theo dõi thời gian" label_end_to_end: "kết thúc đến kết thúc" label_end_to_start: "kết thúc đến bắt đầu" label_enumeration_new: "Giá trị liệt kê mới" label_enumeration_value: "Giá trị liệt kê" label_enumerations: "Liệt kê" label_enterprise: "Doanh nghiệp" - label_enterprise_active_users: "%{current}/%{limit} người dùng đang hoạt động đã đặt chỗ" + label_enterprise_active_users: "%{current}/%{limit} đã đăng ký người dùng đang hoạt động" label_enterprise_edition: "Phiên bản doanh nghiệp" label_enterprise_support: "Hỗ trợ doanh nghiệp" label_environment: "Môi trường" - label_estimates_and_progress: "Ước lượng và tiến độ" + label_estimates_and_progress: "Ước tính và tiến độ" label_equals: "là" - label_equals_with_descendants: "is any with descendants" + label_equals_with_descendants: "có ai có con cháu không" label_everywhere: "mọi nơi" label_example: "Ví dụ" - label_experimental: "Thử nghiệm" - label_i_am_member: "Tôi là thành viên" - label_ifc_viewer: "Xem IFC" - label_ifc_model_plural: "Các mô hình IFC" + label_experimental: "thực nghiệm" + label_i_am_member: "tôi là thành viên" + label_ifc_viewer: "Trình xem Ifc" + label_ifc_model_plural: "Mô hình Ifc" label_import: "Nhập" label_export_to: "Cũng có sẵn tại:" label_expand: "Mở rộng" label_expanded_click_to_collapse: "Mở rộng. Bấm vào để thu gọn" label_f_hour: "%{value} giờ" label_f_hour_plural: "%{value} giờ" - label_favorite: "Yêu thích" + label_favorite: "yêu thích" label_feed_plural: "Nguồn cấp dữ liệu" label_feeds_access_key: "Khóa truy cập RSS" label_feeds_access_key_created_on: "Khoá truy cập API đựơc tạo cách đây %{value}" label_feeds_access_key_type: "RSS" - label_file_plural: "Tập tin" + label_file_plural: "tập tin" label_filter: "Bộ lọc" label_filter_add: "Thêm bộ lọc" - label_filter_by: "Filter by" - label_filter_any_name_attribute: "Name attributes" + label_filter_by: "Lọc theo" + label_filter_any_name_attribute: "Thuộc tính tên" label_filter_plural: "Bộ lọc" label_filters_toggle: "Hiển thị/ẩn bộ lọc" label_float: "Số thực" @@ -3213,123 +3211,123 @@ vi: label_follows: "theo dõi" label_force_user_language_to_default: "Có một ngôn ngữ không được cho phép để mặc định trong thiết lập ngôn ngữ cho người sử dụng" label_form_configuration: "Mẫu cấu hình" - label_formula: "Formula" + label_formula: "Công thức" label_gantt_chart: "Biểu đồ Gantt" - label_gantt_chart_plural: "Biểu đồ Gantt" + label_gantt_chart_plural: "biểu đồ Gantt" label_general: "Tổng quan" label_generate_key: "Tạo khóa" - label_global_modules: "Global modules" + label_global_modules: "Mô-đun toàn cầu" label_global_roles: "Vai trò toàn cầu" label_git_path: "Đường dẫn đến thư mục .git" label_greater_or_equal: ">=" label_group_by: "Nhóm theo" label_group_new: "Nhóm mới" - label_group: "Nhóm" + label_group: "nhóm" label_group_named: "Nhóm %{name}" label_group_plural: "Các Nhóm" label_help: "Trợ giúp" label_here: đây label_hide: "Ẩn" label_history: "Lịch sử" - label_hierarchy: "Phân cấp" + label_hierarchy: "Hệ thống phân cấp" label_hierarchy_leaf: "Hệ thống phân cấp lá" label_home: "Trang chủ" label_subject_or_id: "Chủ đề hoặc Mã" label_calendar_subscriptions: "Đăng ký lịch" - label_identifier: "Định danh" + label_identifier: "định danh" label_in: "trong" label_in_less_than: "ít hơn" label_in_more_than: "nhiều hơn" label_inactive: "Không hoạt động" label_incoming_emails: "Các thư đến" label_includes: "bao gồm" - label_incomplete: Chưa hoàn tất - label_include_sub_projects: Bao gồm dự án con + label_incomplete: không đầy đủ + label_include_sub_projects: Bao gồm các tiểu dự án label_index_by_date: "Chỉ mục theo ngày" label_index_by_title: "Chỉ mục theo tiêu đề" - label_information: "Thông tin" + label_information: "thông tin" label_information_plural: "Thông tin" label_installation_guides: "Hướng dẫn cài đặt" label_integer: "Số nguyên" - label_interface: "Interface" + label_interface: "Giao diện" label_internal: "Nội bộ" - label_introduction_video: "Getting started video" + label_introduction_video: "Video bắt đầu" label_invite_user: "Mời người dùng" - label_item: "Item" - label_item_plural: "Items" - label_weighted_item_list: "Weighted item list" - label_share: "Chia sẻ" + label_item: "mục" + label_item_plural: "Mặt hàng" + label_weighted_item_list: "Danh sách mặt hàng có trọng số" + label_share: "chia sẻ" label_share_project_list: "Chia sẻ danh sách dự án" label_share_work_package: "Chia sẻ gói công việc" label_show_all_registered_users: "Hiển thị tất cả người dùng đã đăng ký" label_show_less: "Hiển thị ít hơn" - label_show_more: "Xem thêm" - label_journal: "Nhật ký" + label_show_more: "Hiển thị thêm" + label_journal: "nhật ký" label_journal_diff: "So sánh mô tả" - label_language: "Ngôn ngữ" - label_languages: "Ngôn ngữ" - label_external_links: "External links" - label_locale: "Language and region" - label_jump_to_a_project: "Nhảy đến dự án..." + label_language: "ngôn ngữ" + label_languages: "ngôn ngữ" + label_external_links: "Liên kết ngoài" + label_locale: "Ngôn ngữ và khu vực" + label_jump_to_a_project: "Chuyển đến một dự án..." label_keyword_plural: "Từ khóa" label_language_based: "Dựa trên ngôn ngữ của người dùng" - label_last_activity: "Hoạt động gần nhất" + label_last_activity: "Hoạt động cuối cùng" label_last_change_on: "Thay đổi cuối trên" - label_last_changes: "cuối %{count} thay đổi" - label_last_login: "Lần đăng nhập trước" + label_last_changes: "%{count} thay đổi gần đây nhất" + label_last_login: "Lần đăng nhập cuối cùng" label_last_month: "tháng trước" - label_last_n_days: "trong %{count} ngày qua" + label_last_n_days: "%{count} ngày qua" label_last_week: "tuần trước" - label_latest_revision: "Phiên bản mới nhất" - label_latest_revision_plural: "Các phiên bản mới nhất" + label_latest_revision: "Bản sửa đổi mới nhất" + label_latest_revision_plural: "Phiên bản mới nhất" label_ldap_authentication: "Xác thực LDAP" label_learn_more: "Tìm hiểu thêm" label_less_or_equal: "<=" - label_less_than_ago: "ít hơn ngày trước" + label_less_than_ago: "chưa đầy ngày trước" label_link_url: "Liên kết (URL)" - label_list: "Danh sách" + label_list: "danh sách" label_loading: "Đang tải..." - label_locked: "đã khóa" + label_locked: "bị khóa" label_lock_user: "Khóa người dùng" - label_logged_as: "Đăng nhập với tư cách" + label_logged_as: "Đăng nhập như" label_login: "Đăng nhập" - label_custom_logo: "Custom logo desktop" - label_custom_logo_mobile: "Custom logo mobile" + label_custom_logo: "Máy tính để bàn logo tùy chỉnh" + label_custom_logo_mobile: "Logo tùy chỉnh di động" label_custom_export_logo: "Logo xuất khẩu tùy chỉnh" - label_custom_export_cover: "Nền bìa xuất khẩu tùy chỉnh" - label_custom_export_footer: "Custom export footer image" - label_custom_export_font_regular: "Regular" - label_custom_export_font_bold: "Bold" - label_custom_export_font_italic: "In nghiêng" - label_custom_export_font_bold_italic: "Bold Italic" - label_custom_export_cover_overlay: "Lớp phủ nền bìa xuất khẩu tùy chỉnh" - label_custom_export_cover_text_color: "Màu chữ" - label_custom_pdf_export_settings: "Cài đặt xuất khẩu PDF tùy chỉnh" + label_custom_export_cover: "Nền bìa xuất tùy chỉnh" + label_custom_export_footer: "Hình ảnh chân trang xuất tùy chỉnh" + label_custom_export_font_regular: "chính quy" + label_custom_export_font_bold: "in đậm" + label_custom_export_font_italic: "Nghiêng" + label_custom_export_font_bold_italic: "In nghiêng đậm" + label_custom_export_cover_overlay: "Lớp phủ nền bìa xuất tùy chỉnh" + label_custom_export_cover_text_color: "Màu văn bản" + label_custom_pdf_export_settings: "Cài đặt xuất PDF tùy chỉnh" label_custom_favicon: "Favicon tuỳ chỉnh" label_custom_touch_icon: "Tùy chỉnh biểu tượng ICON" label_logout: "Đăng xuất" - label_mapping_for: "Mapping for: %{attribute}" + label_mapping_for: "Ánh xạ cho: %{attribute}" label_main_menu: "Menu bên" label_manage: "Quản lý" label_manage_groups: "Quản lý nhóm" - label_managed_repositories_vendor: "Các kho lưu trữ %{vendor} được quản lý" - label_mathematical_operators: "Mathematical operators" + label_managed_repositories_vendor: "Kho lưu trữ %{vendor} được quản lý" + label_mathematical_operators: "Toán tử toán học" label_max_size: "Kích thước tối đa" label_me: "tôi" label_member_new: "Thành viên mới" - label_member_all_admin: "(Tất cả vai trò do trạng thái quản trị viên)" - label_member_plural: "Thành viên" - label_membership_plural: "Thành viên" - label_membership_added: "Thành viên đã thêm" - label_membership_updated: "Thành viên đã cập nhật" - label_menu: "Trình đơn" + label_member_all_admin: "(Tất cả các vai trò do tư cách quản trị viên)" + label_member_plural: "thành viên" + label_membership_plural: "Tư cách thành viên" + label_membership_added: "Đã thêm thành viên" + label_membership_updated: "Đã cập nhật thành viên" + label_menu: "thực đơn" label_menu_badge: - pre_alpha: "pre-alpha" + pre_alpha: "tiền alpha" alpha: "alpha" - beta: "beta" - label_menu_item_name: "Tên mục menu" - label_message: "Tin nhắn" - label_message_last: "Tin nhắn gần nhất" + beta: "THỬ NGHIỆM" + label_menu_item_name: "Tên món trong thực đơn" + label_message: "tin nhắn" + label_message_last: "Tin nhắn cuối cùng" label_message_new: "Tin nhắn mới" label_message_plural: "Tin nhắn" label_message_posted: "Đã thêm tin nhắn" @@ -3340,21 +3338,21 @@ vi: label_modification: "thay đổi %{count}" label_modified: "đã sửa đổi" label_module_plural: "Các Mô-Đun" - label_modules: "Các Mô-Đun" + label_modules: "mô-đun" label_months_from: "tháng từ" label_more: "Xem thêm" - label_more_information: "Thông tin thêm" + label_more_information: "Thêm thông tin" label_more_than_ago: "nhiều hơn mấy ngày trước" label_move_column_left: "Di chuyển cột sang trái" label_move_column_right: "Di chuyển cột sang phải" label_move_work_package: "Di chuyển work package" - label_my_account: "Account settings" + label_my_account: "Cài đặt tài khoản" label_my_activity: "Hoạt động của tôi" label_my_account_data: "Dữ liệu tài khoản của tôi" - label_my_avatar: "Ảnh đại diện của tôi" + label_my_avatar: "Hình đại diện của tôi" label_my_queries: "Các truy vấn tùy biến của tôi" - label_name: "Tên" - label_never: "Không bao giờ" + label_name: "tên" + label_never: "không bao giờ" label_new: "Mới" label_new_features: "Tính năng mới" label_new_statuses_allowed: "Trạng thái mới được phép" @@ -3364,7 +3362,7 @@ vi: label_news_latest: "Tin mới" label_news_new: "Thêm tin" label_news_edit: "Chỉnh sửa tin" - label_news_plural: "Tin tức" + label_news_plural: "tin tức" label_news_view_all: "Xem tất cả tin" label_next: "Tiếp" label_next_week: "Tuần tới" @@ -3373,31 +3371,31 @@ vi: label_no_due_date: "không có ngày kết thúc" label_no_start_date: "không có ngày bắt đầu" label_no_parent_page: "Không có trang quan hệ" - label_notification_center_plural: "Thông báo" + label_notification_center_plural: "thông báo" label_nothing_display: "Không có gì để hiển thị" label_nobody: "không ai" - label_not_configured: "Not configured" + label_not_configured: "Chưa được định cấu hình" label_not_found: "không tìm thấy" label_none: "không" label_none_parentheses: "(trống)" label_not_contains: "không chứa" label_not_equals: "không là" - label_life_cycle_step_plural: "Project life cycle" - label_on: "vào" - label_operator_all: "không rỗng" - label_operator_none: "rỗng" + label_life_cycle_step_plural: "Vòng đời dự án" + label_on: "trên" + label_operator_all: "không trống" + label_operator_none: "trống rỗng" label_operator_equals_or: "là (HOẶC)" label_operator_equals_all: "là (VÀ)" label_operator_shared_with_user_any: "bất kỳ" label_open: "mở" - label_closed: "đã đóng" + label_closed: "đóng cửa" label_open_menu: "Mở trình đơn" label_open_work_packages: "mở" label_open_work_packages_plural: "mở" label_openproject_website: "Trang web OpenProject" - label_optional_description: "Mô tả" + label_optional_description: "mô tả" label_options: "Tuỳ chọn" - label_other: "Khác" + label_other: "khác" label_overall_activity: "Tổng thể hoạt động" label_overview: "Tổng quan" label_page_title: "Tiêu đề trang" @@ -3409,17 +3407,17 @@ vi: label_password_rule_uppercase: "Chữ hoa" label_path_encoding: "Mã hóa đường dẫn" label_per_page: "Mỗi trang:" - label_people: "Người" + label_people: "mọi người" label_permissions: "Phân Quyền" label_permissions_report: "Báo cáo phân quyền" label_personalize_page: "Cá nhân hoá các trang này" - label_placeholder_user: "Người dùng tạm thời" - label_placeholder_user_new: "Người dùng giả lập mới" - label_placeholder_user_plural: "Người dùng giả định" + label_placeholder_user: "Người dùng giữ chỗ" + label_placeholder_user_new: "Người dùng giữ chỗ mới" + label_placeholder_user_plural: "Người dùng giữ chỗ" label_planning: "Lập kế hoạch" label_please_login: "Vui lòng đăng nhập" label_plugins: "Plugins" - label_portfolio_plural: "Portfolios" + label_portfolio_plural: "danh mục đầu tư" label_modules_and_plugins: "Mô-đun và Plugin" label_precedes: "trước" label_preferences: "Tùy chỉnh" @@ -3433,51 +3431,51 @@ vi: label_product_version: "Phiên bản sản phẩm" label_profile: "Hồ sơ" label_percent_complete: "% Hoàn thành" - label_progress_tracking: "Progress tracking" - label_project: "Dự án" + label_progress_tracking: "Theo dõi tiến độ" + label_project: "dự án" label_project_activity: "Hoạt động dự án" - label_project_attribute_plural: "Các thuộc tính dự án" + label_project_attribute_plural: "Thuộc tính dự án" label_project_attribute_manage_link: "Quản lý thuộc tính dự án" label_project_count: "Tổng số dự án" label_project_copy_notifications: "Gửi email thông báo trong quá trình sao chép dự án" - label_project_initiation_export_pdf: "Export PDF for %{project_creation_name}" + label_project_initiation_export_pdf: "Xuất PDF cho %{project_creation_name}" label_project_latest: "Dự án mới nhất" label_project_default_type: "Cho phép kiểu rỗng" label_project_hierarchy: "Phân cấp dự án" - label_project_mappings: "Các dự án" + label_project_mappings: "dự án" label_project_new: "Dự án mới" - label_project_plural: "Các dự án" + label_project_plural: "dự án" label_project_list_plural: "Danh sách dự án" - label_project_life_cycle: "Project life cycle" - label_project_attributes_plural: "Các thuộc tính dự án" - label_project_custom_field_plural: "Các thuộc tính dự án" + label_project_life_cycle: "Vòng đời dự án" + label_project_attributes_plural: "Thuộc tính dự án" + label_project_custom_field_plural: "Thuộc tính dự án" label_project_settings: "Thiết lập dự án" label_project_attributes_settings: "Cài đặt thuộc tính dự án" - label_project_storage_plural: "File storages" - label_project_storage_project_folder: "File storages: Project folders" - label_projects_disk_usage_information: "%{count} dự án đang sử dụng %{used_disk_space} dung lượng đĩa" + label_project_storage_plural: "Kho lưu trữ tập tin" + label_project_storage_project_folder: "Kho lưu trữ tệp: Thư mục dự án" + label_projects_disk_usage_information: "%{count} dự án sử dụng dung lượng đĩa %{used_disk_space}" label_project_view_all: "Xem tất cả các dự án" label_project_show_details: "Hiển thị chi tiết dự án" label_project_hide_details: "Ẩn chi tiết Dự án" - label_portfolio: "Portfolio" - label_portfolio_new: "New portfolio" - label_program: "Program" - label_program_new: "New program" + label_portfolio: "danh mục đầu tư" + label_portfolio_new: "Danh mục đầu tư mới" + label_program: "chương trình" + label_program_new: "Chương trình mới" label_public_projects: "Các dự án công khai" label_query_new: "Truy vấn mới" label_query_plural: "Truy vấn tùy chỉnh" label_read: "Đọc..." - label_read_documentation: "Read documentation" + label_read_documentation: "Đọc tài liệu" label_register: "Tạo tài khoản mới" label_register_with_developer: "Đăng ký là nhà phát triển" label_registered_on: "Ngày tham gia" label_related_work_packages: "Work package liên quan" label_relates: "liên quan đến" label_relates_to: "liên quan đến" - label_relation: "Relation" - label_relation_actions: "Relation actions" + label_relation: "quan hệ" + label_relation_actions: "Hành động quan hệ" label_relation_delete: "Xóa quan hệ" - label_relation_edit: "Edit relation" + label_relation_edit: "Chỉnh sửa mối quan hệ" label_relation_new: "Quan hệ mới" label_release_notes: "Ghi chú phát hành" label_remaining_work: "Công việc còn lại" @@ -3491,11 +3489,11 @@ vi: label_reported_work_packages: "Các gói công việc đã báo cáo" label_reporting: "Đang báo cáo" label_reporting_plural: "Đang báo cáo" - label_repository: "Kho lưu trữ" - label_repository_remove: "Remove repository" - label_repository_root: "Gốc kho lưu trữ" + label_repository: "kho lưu trữ" + label_repository_remove: "Xóa kho lưu trữ" + label_repository_root: "Kho lưu trữ gốc" label_repository_plural: "Kho lưu trữ" - label_request_submission: "Request submission" + label_request_submission: "Yêu cầu gửi" label_required: "bắt buộc" label_requires: "bắt buộc" label_result_plural: "Các kết quả" @@ -3506,21 +3504,21 @@ vi: label_roadmap_edit: "Sửa lộ trình %{name}" label_roadmap_due_in: "Hết hạn trong %{value}" label_roadmap_no_work_packages: "Không có công việc nào cho phiên bản này" - label_roadmap_overdue: "%{value} trễ hạn" + label_roadmap_overdue: "%{value} muộn" label_role_and_permissions: "Vai trò và Quyền hạn" label_role_new: "Vai trò mới" - label_role_grantable: "Grantable role" + label_role_grantable: "Vai trò có thể cấp" label_role_plural: "Vai trò" label_role_search: "Chỉ định vai trò cho các thành viên mới" label_scm: "SCM" - label_scroll_left: "Scroll left" - label_scroll_right: "Scroll right" + label_scroll_left: "Cuộn sang trái" + label_scroll_right: "Cuộn sang phải" label_search: "Tìm kiếm" label_search_by_name: "Tìm kiếm theo tên" - label_send_information: "Gửi thông tin đăng nhập mới cho người dùng" + label_send_information: "Gửi thông tin xác thực mới cho người dùng" label_send_test_email: "Gửi email thử nghiệm" - label_session: "Phiên làm việc" - label_setting_plural: "Cài đặt" + label_session: "Phiên" + label_setting_plural: "cài đặt" label_system_settings: "Cài đặt hệ thống" label_show_completed_versions: "Xem phiên bản đã hoàn thành" label_columns: "Cột" @@ -3533,48 +3531,48 @@ vi: label_sort_highest: "Lên trên cùng" label_sort_lower: "Dịch xuống" label_sort_lowest: "Chuyển đến dưới cùng" - label_spent_time: "Thời gian" + label_spent_time: "dành thời gian" label_start_to_end: "bắt đầu đến kết thúc" label_start_to_start: "bắt đầu đến bắt đầu" label_statistics: "Số liệu thống kê" - label_status: "Trạng thái" + label_status: "trạng thái" label_storage_free_space: "Dung lượng đĩa còn lại" label_storage_used_space: "Dung lượng đĩa đã sử dụng" - label_storage_group: "Hệ thống tệp lưu trữ %{identifier}" - label_storage_for: "Bao gồm lưu trữ cho" - label_string: "Văn bản" - label_subproject: "Dự án con" + label_storage_group: "Hệ thống tập tin lưu trữ %{identifier}" + label_storage_for: "Bao gồm việc lưu trữ cho" + label_string: "văn bản" + label_subproject: "dự án con" label_subproject_new: "Thêm dự án con" label_subproject_plural: "Dự án con" - label_subitems: "Subitems" + label_subitems: "mục phụ" label_subtask_plural: "Tác vụ con" label_summary: "Tóm tắt" label_system: "Hệ thống" label_system_storage: "Thông tin lưu trữ" label_table_of_contents: "Mục lục" label_tag: "Từ khóa" - label_team_planner: "Lập kế hoạch nhóm" - label_template: "Mẫu" - label_templates: "Templates" + label_team_planner: "Người lập kế hoạch nhóm" + label_template: "mẫu" + label_templates: "mẫu" label_text: "Văn bản dài" label_this_month: "tháng này" label_this_week: "tuần này" label_this_year: "năm này" - label_time: "Thời gian" - label_time_entry_plural: "Thời gian" - label_time_entry_activity_plural: "Hoạt động thời gian đã tiêu tốn" - label_title: "Tiêu đề" - label_projects_menu: "Các dự án" + label_time: "thời gian" + label_time_entry_plural: "dành thời gian" + label_time_entry_activity_plural: "Hoạt động dành thời gian" + label_title: "tiêu đề" + label_projects_menu: "dự án" label_today: "hôm nay" - label_token_version: "Token Version" - label_today_as_start_date: "Select today as start date." - label_today_as_due_date: "Select today as finish date." - label_today_as_date: "Select today as date." + label_token_version: "Phiên bản mã thông báo" + label_today_as_start_date: "Chọn hôm nay làm ngày bắt đầu." + label_today_as_due_date: "Chọn hôm nay làm ngày kết thúc." + label_today_as_date: "Chọn hôm nay làm ngày." label_top_menu: "Trình đơn trên cùng" label_topic_plural: "Các chủ đề" label_total: "Tổng cộng" label_type_new: "Kiểu mới" - label_type_plural: "Các loại" + label_type_plural: "các loại" label_ui: "Giao diện người dùng" label_updated_time: "Cập nhật cách đây %{value}" label_updated_time_at: "%{author} %{age}" @@ -3587,23 +3585,23 @@ vi: label_user_and_permission: "Người dùng và quyền" label_user_named: "Người dùng %{name}" label_user_activity: "%{value} hoạt động" - label_user_anonymous: "Ẩn danh" + label_user_anonymous: "vô danh" label_user_mail_option_all: "Bất kỳ sự kiện trên tất cả dự án của tôi" label_user_mail_option_none: "Không có sự kiện" label_user_mail_option_only_assigned: "Chỉ những thứ tôi được phân công" label_user_mail_option_only_my_events: "Chỉ những thứ tôi theo dõi hoặc liên quan" label_user_mail_option_only_owner: "Chỉ những thứ tôi sở hữu" - label_user_mail_option_selected: "Chỉ cho sự kiện trên các dự án đã chọn" - label_user_menu: "User menu" + label_user_mail_option_selected: "Chỉ dành cho bất kỳ sự kiện nào trên các dự án đã chọn" + label_user_menu: "Trình đơn người dùng" label_user_new: "Người dùng mới" label_user_plural: "Người dùng" label_user_search: "Tìm kiếm người dùng" label_user_settings: "Cài đặt người dùng" label_users_settings: "Cài đặt người dùng" - label_value_x: "Value: %{x}" + label_value_x: "Giá trị: %{x}" label_version_new: "Phiên bản mới" label_version_edit: "Chỉnh sửa phiên bản" - label_version_plural: "Các phiên bản" + label_version_plural: "phiên bản" label_version_sharing_descendants: "Với các dự án con" label_version_sharing_hierarchy: "Với phân cấp dự án" label_version_sharing_none: "Không được chia sẻ" @@ -3616,8 +3614,8 @@ vi: label_watched_work_packages: "Các công việc đã xem" label_what_is_this: "Đây là gì?" label_week: "Tuần" - label_widget: "Widget" - label_widget_new: "New widget" + label_widget: "tiện ích" + label_widget_new: "Tiện ích mới" label_wiki_content_added: "Đã thêm trang Wiki" label_wiki_content_updated: "Trang Wiki đã được cập nhật" label_wiki_toc: "Bảng mục lục" @@ -3628,19 +3626,19 @@ vi: label_wiki_page_attachments: "Tệp đính kèm trang Wiki" label_wiki_page_id: "ID trang Wiki" label_wiki_navigation: "Điều hướng Wiki" - label_wiki_page: "Trang wiki" + label_wiki_page: "trang Wiki" label_wiki_page_plural: "Trang wiki" label_wiki_show_index_page_link: "Hiển thị mục menu con 'Mục lục'" label_wiki_show_menu_item: "Hiển thị dưới dạng mục menu trong điều hướng dự án" label_wiki_show_new_page_link: "Hiển thị mục menu con 'Tạo trang con mới'" label_wiki_show_submenu_item: "Hiển thị dưới dạng mục menu con của" label_wiki_start: "Trang bắt đầu" - label_work: "Công việc" + label_work: "làm việc" label_work_package: "Work Package" label_work_package_attachments: "Phần đính kèm công việc" label_work_package_category_new: "Danh mục mới" label_work_package_category_plural: "Các thể loại work package" - label_work_package_comments: "Work package comments" + label_work_package_comments: "Nhận xét gói công việc" label_work_package_hierarchy: "Phân cấp công việc" label_work_package_new: "Work package mới" label_work_package_edit: "Chỉnh sửa work package %{name}" @@ -3677,19 +3675,19 @@ vi: other: "%{count} dự án" zero: "Không có dự án" label_x_files: - one: "1 tệp" + one: "1 tập tin" other: "%{count} tệp" - zero: "không có tệp" + zero: "không có tập tin" label_yesterday: "ngày hôm qua" label_zen_mode: "Chế độ Zen" - label_role_type: "Kiểu" + label_role_type: "loại" label_member_role: "Vai trò dự án" label_global_role: "Vai trò toàn cầu" label_not_changeable: "(không thể thay đổi)" - label_global: "Toàn cầu" - label_seeded_from_env_warning: This record has been created through a setting environment variable. It is not editable through UI. + label_global: "toàn cầu" + label_seeded_from_env_warning: Bản ghi này đã được tạo thông qua một biến môi trường cài đặt. Nó không thể chỉnh sửa thông qua giao diện người dùng. macro_execution_error: "Lỗi khi thực thi macro %{macro_name}" - macro_unavailable: "Macro %{macro_name} không thể được hiển thị." + macro_unavailable: "Macro %{macro_name} không thể hiển thị." macros: placeholder: "[Placeholder] Macro %{macro_name}" errors: @@ -3697,25 +3695,25 @@ vi: legacy_warning: timeline: "Macro này đã bị xóa. Bạn có thể thay thế các chức năng với một table macro." include_wiki_page: - removed: "Macro này không còn tồn tại." + removed: "Macro không còn tồn tại." wiki_child_pages: errors: page_not_found: "Không tim thấy trang wiki %{name}." create_work_package_link: errors: - no_project_context: "Gọi macro create_work_package_link từ ngoài ngữ cảnh dự án." - invalid_type: "Không tìm thấy loại với tên '%{type}' trong dự án '%{project}'." + no_project_context: "Gọi macro create_work_package_link từ ngữ cảnh bên ngoài dự án." + invalid_type: "Không tìm thấy loại nào có tên '%{type}' trong dự án '%{project}'." link_name: "Work package mới" link_name_type: "Mới %{type_name}" mail: - actions: "Hành động" + actions: "hành động" digests: - including_mention_singular: "bao gồm một lời nhắc" - including_mention_plural: "bao gồm %{number_mentioned} lời nhắc" + including_mention_singular: "trong đó có đề cập đến" + including_mention_plural: "bao gồm %{number_mentioned} lượt đề cập" unread_notification_singular: "1 thông báo chưa đọc" unread_notification_plural: "%{number_unread} thông báo chưa đọc" - you_have: "Bạn có" - logo_alt_text: "Logo" + you_have: "bạn có" + logo_alt_text: "biểu tượng" mention: subject: "%{user_name} đã nhắc đến bạn trong #%{id} - %{subject}" notification: @@ -3723,72 +3721,72 @@ vi: see_in_center: "Xem bình luận trong trung tâm thông báo" settings: "Thay đổi cài đặt email" salutation: "Xin chào %{user}" - salutation_full_name: "Họ và tên đầy đủ" + salutation_full_name: "Tên đầy đủ" work_packages: - created_at: "Tạo vào %{timestamp} bởi %{user}" - login_to_see_all: "Đăng nhập để xem tất cả thông báo." - mentioned: "Bạn đã được nhắc đến trong một bình luận" - mentioned_by: "%{user} đã nhắc đến bạn trong một bình luận" + created_at: "Được tạo tại %{timestamp} bởi %{user}" + login_to_see_all: "Đăng nhập để xem tất cả các thông báo." + mentioned: "Bạn đã được đề cập trong một nhận xét" + mentioned_by: "%{user} đã đề cập đến bạn trong nhận xét" more_to_see: other: "Có thêm %{count} gói công việc có thông báo." open_in_browser: "Mở trong trình duyệt" reason: - watched: "Đã theo dõi" - assigned: "Được chỉ định" - responsible: "Trách nhiệm" - mentioned: "Nhắc đến" - shared: "Được chia sẻ" + watched: "Đã xem" + assigned: "được giao" + responsible: "chịu trách nhiệm" + mentioned: "Được đề cập" + shared: "đã chia sẻ" subscribed: "tất cả" - prefix: "Nhận được do cài đặt thông báo: %{reason}" + prefix: "Đã nhận được do cài đặt thông báo: %{reason}" date_alert_start_date: "Cảnh báo ngày" date_alert_due_date: "Cảnh báo ngày" - reminder: "Reminder" + reminder: "Lời nhắc" see_all: "Xem tất cả" - updated_at: "Cập nhật vào %{timestamp} bởi %{user}" + updated_at: "Cập nhật tại %{timestamp} bởi %{user}" reminder_notifications: - subject: "Reminder: %{note}" - heading: "You have a new reminder" - note: "Note: “%{note}”" + subject: "Nhắc nhở: %{note}" + heading: "Bạn có lời nhắc mới" + note: "Lưu ý: “%{note}”" sharing: work_packages: - allowed_actions: "Bạn có thể %{allowed_actions} gói công việc này. Điều này có thể thay đổi tùy thuộc vào vai trò và quyền hạn của bạn trong dự án." - create_account: "Để truy cập gói công việc này, bạn sẽ cần tạo và kích hoạt tài khoản trên %{instance}." + allowed_actions: "Bạn có thể %{allowed_actions} gói công việc này. Điều này có thể thay đổi tùy thuộc vào vai trò và quyền dự án của bạn." + create_account: "Để truy cập gói công việc này, bạn cần tạo và kích hoạt tài khoản trên %{instance}." open_work_package: "Mở gói công việc" subject: "Gói công việc #%{id} đã được chia sẻ với bạn" - enterprise_text: "Chia sẻ gói công việc với người dùng không phải là thành viên của dự án." + enterprise_text: "Chia sẻ các gói công việc với người dùng không phải là thành viên của dự án." summary: - user: "%{user} đã chia sẻ một gói công việc với bạn với quyền %{role_rights}" - group: "%{user} đã chia sẻ một gói công việc với nhóm %{group} mà bạn là thành viên" + user: "%{user} đã chia sẻ gói công việc với bạn với quyền %{role_rights}" + group: "%{user} đã chia sẻ gói công việc với nhóm %{group} bạn là thành viên của" storages: health: plaintext: storage: "Lưu trữ" healthy: - summary: 'Tin vui! Tình trạng lưu trữ của bạn, %{storage_name}, hiện đang hiển thị là "Khỏe mạnh".' - error-solved-on: "Giải quyết vào" - recommendation: "Chúng tôi sẽ tiếp tục theo dõi hệ thống để đảm bảo nó vẫn trong tình trạng tốt. Trong trường hợp có sự bất thường, chúng tôi sẽ thông báo cho bạn." - details: "Để biết thêm chi tiết hoặc thực hiện các sửa đổi cần thiết, bạn có thể truy cập cấu hình lưu trữ của mình" + summary: 'Tin tốt! Trạng thái bộ nhớ của bạn, %{storage_name}, hiện đang hiển thị là "Ổn".' + error-solved-on: "Đã giải quyết trên" + recommendation: "Chúng tôi sẽ tiếp tục theo dõi hệ thống để đảm bảo hệ thống vẫn hoạt động tốt. Trong trường hợp có bất kỳ sự khác biệt nào, chúng tôi sẽ thông báo cho bạn." + details: "Để biết thêm chi tiết hoặc thực hiện bất kỳ sửa đổi cần thiết nào, bạn có thể truy cập cấu hình bộ nhớ của mình" unhealthy: - summary: 'Tình trạng lưu trữ của bạn, %{storage_name}, hiện đang hiển thị là "Lỗi". Chúng tôi đã phát hiện một vấn đề có thể yêu cầu sự chú ý của bạn.' + summary: 'Trạng thái bộ nhớ của bạn, %{storage_name}, hiện đang hiển thị là "Lỗi". Chúng tôi đã phát hiện một sự cố có thể cần bạn chú ý.' error-details: "Chi tiết lỗi" error-message: "Thông báo lỗi" - error-occurred-on: "Xảy ra vào" - recommendation: "Chúng tôi khuyên bạn nên truy cập trang cấu hình lưu trữ để giải quyết vấn đề này" - unsubscribe: "Nếu bạn không còn muốn nhận các thông báo này, bạn có thể hủy đăng ký bất cứ lúc nào. Để hủy đăng ký, vui lòng làm theo hướng dẫn trên trang này" - email_notification_settings: "Cài đặt thông báo qua email cho lưu trữ" + error-occurred-on: "Đã xảy ra vào ngày" + recommendation: "Chúng tôi khuyên bạn nên truy cập trang cấu hình bộ nhớ để giải quyết vấn đề này" + unsubscribe: "Nếu bạn không muốn nhận những thông báo này nữa, bạn có thể hủy đăng ký bất kỳ lúc nào. Để hủy đăng ký, vui lòng làm theo hướng dẫn trên trang này" + email_notification_settings: "Cài đặt thông báo email lưu trữ" see_storage_settings: "Xem cài đặt lưu trữ" healthy: - subject: 'Lưu trữ "%{name}" hiện đã khỏe mạnh!' - solved_at: "giải quyết vào" - summary: "Vấn đề với tích hợp lưu trữ %{storage_name} của bạn đã được giải quyết" + subject: 'Bộ nhớ "%{name}" hiện đã hoạt động bình thường!' + solved_at: "giải quyết tại" + summary: "Sự cố với việc tích hợp bộ nhớ %{storage_name} của bạn hiện đã được giải quyết" unhealthy: - subject: 'Lưu trữ "%{name}" không khỏe mạnh!' - since: "kể từ" - summary: "Có vấn đề với tích hợp lưu trữ %{storage_name} của bạn" + subject: 'Bộ nhớ "%{name}" không tốt cho sức khỏe!' + since: "kể từ khi" + summary: "Đã xảy ra sự cố với việc tích hợp bộ nhớ %{storage_name} của bạn" troubleshooting: - text: "Để biết thêm thông tin, hãy kiểm tra các lưu trữ tệp" - link_text: "tài liệu xử lý sự cố" - mail_body_account_activation_request: "Một người dùng mới (%{value}) đã đăng ký. Tài khoản đang chờ phê duyệt của bạn:" + text: "Để biết thêm thông tin, hãy kiểm tra kho lưu trữ tệp" + link_text: "tài liệu khắc phục sự cố" + mail_body_account_activation_request: "Một người dùng mới (%{value}) đã đăng ký. Tài khoản đang chờ bạn phê duyệt:" mail_body_account_information: "Thông tin tài khoản của bạn" mail_body_account_information_external: "Bạn có thể sử dụng tài khoản %{value} của mình để đăng nhập." mail_body_backup_ready: "Yêu cầu sao lưu của bạn đã sẵn sàng. Bạn có thể tải về tại đây:" @@ -3797,32 +3795,32 @@ vi: mail_body_backup_token_info: Token trước đó không còn hiệu lực. mail_body_backup_waiting_period: Mã token sao lưu sẽ được kích hoạt trong %{hours} giờ. mail_body_backup_token_warning: Nếu đây không phải là bạn, hãy đăng nhập vào OpenProject ngay lập tức và thiết lập lại lại. - mail_body_incoming_email_error: Email bạn gửi đến OpenProject không thể được xử lý. - mail_body_incoming_email_error_in_reply_to: "Vào %{received_at} %{from_email} đã viết" + mail_body_incoming_email_error: Không thể xử lý email bạn gửi tới OpenProject. + mail_body_incoming_email_error_in_reply_to: "Tại %{received_at} %{from_email} đã viết" mail_body_incoming_email_error_logs: "Nhật ký" mail_body_lost_password: "Để thay đổi mật khẩu của bạn, hãy nhấp vào liên kết sau:" mail_password_change_not_possible: title: "Không thể thay đổi mật khẩu" body: "Tài khoản của bạn tại %{app_title} được kết nối với nhà cung cấp xác thực bên ngoài (%{name})." - subtext: "Mật khẩu cho tài khoản bên ngoài không thể thay đổi trong ứng dụng. Vui lòng sử dụng chức năng quên mật khẩu của nhà cung cấp xác thực của bạn." + subtext: "Mật khẩu cho tài khoản bên ngoài không thể thay đổi trong ứng dụng. Vui lòng sử dụng chức năng mất mật khẩu của nhà cung cấp xác thực của bạn." mail_body_register: "Chào mừng đến với %{app_title}. Vui lòng kích hoạt tài khoản của bạn bằng cách nhấp vào liên kết này:" mail_body_register_header_title: "Email mời thành viên dự án" - mail_body_register_user: "Kính gửi %{name}, " + mail_body_register_user: "Kính gửi %{name}," mail_body_register_links_html: | - Vui lòng cảm thấy tự do truy cập kênh YouTube của chúng tôi (%{youtube_link}), nơi chúng tôi cung cấp một hội thảo trên web (%{webinar_link}) - và các video "Bắt đầu" (%{get_started_link}) để làm cho những bước đầu tiên của bạn trong OpenProject trở nên dễ dàng nhất có thể. + Vui lòng duyệt qua kênh youtube của chúng tôi (%{youtube_link}) nơi chúng tôi cung cấp hội thảo trên web (%{webinar_link}) + và video “Bắt đầu” (%{get_started_link}) để thực hiện các bước đầu tiên của bạn trong OpenProject dễ dàng nhất có thể.
    - Nếu bạn có bất kỳ câu hỏi nào thêm, hãy tham khảo tài liệu của chúng tôi (%{documentation_link}) hoặc liên hệ với quản trị viên của bạn. + Nếu bạn có thêm bất kỳ câu hỏi nào, hãy tham khảo tài liệu của chúng tôi (%{documentation_link}) hoặc liên hệ với quản trị viên của bạn. mail_body_register_closing: "Nhóm OpenProject của bạn" - mail_body_register_ending: "Giữ kết nối! Trân trọng," - mail_body_reminder: "%{count} gói công việc(s) được giao cho bạn sẽ phải hoàn thành trong %{days} ngày tới:" + mail_body_register_ending: "Luôn kết nối! Trân trọng," + mail_body_reminder: "%{count} gói công việc được giao cho bạn sẽ đến hạn trong %{days} ngày tới:" mail_body_group_reminder: '%{count} gói công việc được gán cho nhóm "%{group}" là do %{days} ngày tiếp theo:' - mail_body_wiki_page_added: "Trang wiki '%{id}' đã được %{author} thêm." - mail_body_wiki_page_updated: "Trang wiki '%{id}' đã được %{author} cập nhật." - mail_subject_account_activation_request: "Yêu cầu kích hoạt tài khoản %{value}" + mail_body_wiki_page_added: "Trang wiki '%{id}' đã được thêm bởi %{author}." + mail_body_wiki_page_updated: "Trang wiki '%{id}' đã được cập nhật bởi %{author}." + mail_subject_account_activation_request: "%{value} yêu cầu kích hoạt tài khoản" mail_subject_backup_ready: "Bản sao lưu của bạn đã sẵn sàng" - mail_subject_backup_token_reset: "Đặt lại mã sao lưu" - mail_subject_incoming_email_error: "Email bạn gửi đến OpenProject không thể được xử lý" + mail_subject_backup_token_reset: "Đặt lại mã thông báo dự phòng" + mail_subject_incoming_email_error: "Không thể xử lý email bạn gửi tới OpenProject" mail_subject_lost_password: "Mật khẩu %{value} của bạn" mail_subject_register: "Kích hoạt tài khoản %{value} của bạn" mail_subject_wiki_content_added: "Trang wiki '%{id}' đã được thêm" @@ -3842,97 +3840,97 @@ vi: with_message: "%{user} đã cập nhật các vai trò bạn có trong dự án '%{project}':" roles: "Bây giờ bạn có các vai trò sau:" mail_member_updated_global: - subject: "Quyền toàn cầu của bạn đã được cập nhật" + subject: "Quyền chung của bạn đã được cập nhật" body: updated_by: without_message: "%{user} đã cập nhật các vai trò của bạn trên toàn cầu." - with_message: "%{user} đã cập nhật các vai trò của bạn trên toàn cầu viết:" + with_message: "%{user} đã cập nhật các vai trò mà bạn đảm nhiệm trên toàn cầu:" roles: "Bây giờ bạn có các vai trò sau:" mail_user_activation_limit_reached: - subject: Đã đạt giới hạn kích hoạt người dùng + subject: Đã đạt đến giới hạn kích hoạt người dùng message: | - Một người dùng mới (%{email}) đã cố gắng tạo một tài khoản trên môi trường OpenProject mà bạn quản lý (%{host}). - Người dùng không thể kích hoạt tài khoản của mình vì đã đạt giới hạn người dùng. + Một người dùng mới (%{email}) đã cố gắng tạo tài khoản trên môi trường OpenProject mà bạn quản lý (%{host}). + Người dùng không thể kích hoạt tài khoản của họ vì đã đạt đến giới hạn người dùng. steps: label: "Để cho phép người dùng đăng nhập, bạn có thể:" - a: "Nâng cấp kế hoạch thanh toán của bạn ([tại đây](upgrade_url))" #here turned into a link - b: "Khóa hoặc xóa một người dùng hiện có ([tại đây](users_url))" #here turned into a link - more_actions: "Thêm chức năng" + a: "Nâng cấp gói thanh toán của bạn ([here](upgrade_url))" #here turned into a link + b: "Khóa hoặc xóa người dùng hiện tại ([here](users_url))" #here turned into a link + more_actions: "Nhiều chức năng hơn" noscript_description: "Bạn cần kích hoạt JavaScript để sử dụng OpenProject!" - noscript_heading: "JavaScript bị tắt" + noscript_heading: "Đã tắt JavaScript" noscript_learn_more: "Tìm hiểu thêm" notice_accessibility_mode: Chế độ tiếp cận có thể được kích hoạt trong [tài khoản settings](url) của bạn. - notice_account_activated: "Tài khoản của bạn đã được kích hoạt. Bạn có thể đăng nhập ngay bây giờ." - notice_account_already_activated: Tài khoản đã được kích hoạt rồi. - notice_account_invalid_token: Mã kích hoạt không hợp lệ + notice_account_activated: "Tài khoản của bạn đã được kích hoạt. Bây giờ bạn có thể đăng nhập." + notice_account_already_activated: Tài khoản đã được kích hoạt. + notice_account_invalid_token: Mã thông báo kích hoạt không hợp lệ notice_account_invalid_credentials: "Tài khoản hoặc mật mã không hợp lệ" - notice_account_invalid_credentials_or_blocked: "Tên người dùng hoặc mật khẩu không hợp lệ hoặc tài khoản bị khóa do nhiều lần đăng nhập không thành công. Nếu vậy, tài khoản sẽ được mở khóa tự động trong thời gian ngắn." - notice_account_lost_email_sent: "Một email với hướng dẫn chọn mật khẩu mới đã được gửi đến bạn." - notice_account_new_password_forced: "Cần một mật khẩu mới." - notice_account_password_expired: "Mật khẩu của bạn đã hết hạn sau %{days} ngày. Vui lòng đặt mật khẩu mới." + notice_account_invalid_credentials_or_blocked: "Người dùng hoặc mật khẩu không hợp lệ hoặc tài khoản bị chặn do đăng nhập nhiều lần không thành công. Nếu vậy, nó sẽ tự động được bỏ chặn trong thời gian ngắn." + notice_account_lost_email_sent: "Một email có hướng dẫn chọn mật khẩu mới đã được gửi cho bạn." + notice_account_new_password_forced: "Cần phải có mật khẩu mới." + notice_account_password_expired: "Mật khẩu của bạn đã hết hạn sau %{days} ngày. Vui lòng đặt một cái mới." notice_account_password_updated: "Mật khẩu đã được cập nhật thành công." - notice_account_pending: "Tài khoản của bạn đã được tạo và hiện đang chờ phê duyệt của quản trị viên." - notice_account_register_done: "Tài khoản đã được tạo thành công. Để kích hoạt tài khoản của bạn, hãy nhấp vào liên kết đã được gửi qua email cho bạn." + notice_account_pending: "Tài khoản của bạn đã được tạo và hiện đang chờ quản trị viên phê duyệt." + notice_account_register_done: "Tài khoản đã được tạo thành công. Để kích hoạt tài khoản của bạn, hãy nhấp vào liên kết được gửi qua email cho bạn." notice_account_unknown_email: "Người dùng không xác định." notice_account_update_failed: "Không thể lưu thiết đặt tài khoản. Xin vui lòng kiểm tra trong trang tài khoản của bạn." notice_account_updated: "Tài khoản đã được cập nhật thành công." notice_account_other_session_expired: "Tất cả phiên giao dịch khác của tài khoản này đã bị vô hiệu." notice_account_wrong_password: "Mật khẩu sai" - notice_account_registered_and_logged_in: "Chào mừng, tài khoản của bạn đã được kích hoạt. Bạn đã đăng nhập thành công." + notice_account_registered_and_logged_in: "Chào mừng, tài khoản của bạn đã được kích hoạt. Bạn đã đăng nhập bây giờ." notice_activation_failed: Tài khoản không thể được kích hoạt. notice_auth_stage_verification_error: "Không thể xác nhận giai đoạn '%{stage}'." notice_auth_stage_wrong_stage: "Dự kiến sẽ kết thúc giai đoạn xác thực '%{expected}', nhưng '%{actual}' quay lại." notice_auth_stage_error: "Xác thực giai đoạn '%{stage}' không thành công." notice_can_t_change_password: "Tài khoản này sử dụng nguồn xác thực bên ngoài. Không thể thay đổi mật khẩu." - notice_custom_options_deleted: "Tùy chọn '%{option_value}' và %{num_deleted} trường hợp của nó đã được xóa." - notice_email_error: "Đã xảy ra lỗi khi gửi email (%{value})" - notice_email_sent: "Một email đã được gửi đến %{value}" - notice_failed_to_save_work_packages: "Không lưu được %{count} gói công việc(s) trên %{total} đã chọn: %{ids}." - notice_failed_to_save_members: "Không thể lưu thành viên(s): %{errors}." + notice_custom_options_deleted: "Tùy chọn '%{option_value}' và các lần xuất hiện %{num_deleted} của nó đã bị xóa." + notice_email_error: "Đã xảy ra lỗi khi gửi thư (%{value})" + notice_email_sent: "Một email đã được gửi tới %{value}" + notice_failed_to_save_work_packages: "Không lưu được %{count} gói công việc trên %{total} đã chọn: %{ids}." + notice_failed_to_save_members: "Không lưu được thành viên: %{errors}." notice_deletion_scheduled: "Việc xóa đã được lên lịch và được thực hiện không đồng bộ." - notice_file_not_found: "Trang bạn đang cố gắng truy cập không tồn tại hoặc đã bị xóa." - notice_forced_logout: "Bạn đã bị đăng xuất tự động sau %{ttl_time} phút không hoạt động." - notice_internal_server_error: "Đã xảy ra lỗi trên trang bạn đang cố gắng truy cập. Nếu bạn tiếp tục gặp vấn đề, vui lòng liên hệ với quản trị viên %{app_title} của bạn để được trợ giúp." - notice_locking_conflict: "Thông tin đã được cập nhật bởi ít nhất một người dùng khác trong thời gian đó." - notice_locking_conflict_additional_information: "Bản cập nhật(s) này đến từ %{users}." - notice_locking_conflict_reload_page: "Vui lòng tải lại trang, xem xét các thay đổi và áp dụng lại các cập nhật của bạn." - notice_locking_conflict_warning: "This page has been updated by someone else. To not lose your edits, copy them locally and reload to view the updated version." - notice_locking_conflict_danger: "Could not save your changes because of conflicting modifications. To not lose your edits, copy them locally and reload to view the updated version." - notice_locking_conflict_action_button: "Discard changes and reload" + notice_file_not_found: "Trang bạn đang cố truy cập không tồn tại hoặc đã bị xóa." + notice_forced_logout: "Bạn đã tự động đăng xuất sau %{ttl_time} phút không hoạt động." + notice_internal_server_error: "Đã xảy ra lỗi trên trang bạn đang cố truy cập. Nếu bạn tiếp tục gặp sự cố, vui lòng liên hệ với quản trị viên %{app_title} của bạn để được hỗ trợ." + notice_locking_conflict: "Thông tin đã được cập nhật bởi ít nhất một người dùng khác trong thời gian chờ đợi." + notice_locking_conflict_additional_information: "(Các) bản cập nhật đến từ %{users}." + notice_locking_conflict_reload_page: "Vui lòng tải lại trang, xem lại các thay đổi và áp dụng lại các cập nhật của bạn." + notice_locking_conflict_warning: "Trang này đã được cập nhật bởi người khác. Để không bị mất các chỉnh sửa của bạn, hãy sao chép chúng trên máy và tải lại để xem phiên bản cập nhật." + notice_locking_conflict_danger: "Không thể lưu các thay đổi của bạn do các sửa đổi xung đột. Để không bị mất các chỉnh sửa của bạn, hãy sao chép chúng trên máy và tải lại để xem phiên bản cập nhật." + notice_locking_conflict_action_button: "Hủy bỏ các thay đổi và tải lại" notice_member_added: Đã thêm %{name} vào dự án. - notice_members_added: Đã thêm %{number} người dùng vào dự án. + notice_members_added: Đã thêm người dùng %{number} vào dự án. notice_member_removed: "Đã xóa %{user} khỏi dự án." notice_member_deleted: "%{user} đã bị xóa khỏi dự án và bị xóa." - notice_no_principals_found: "Không tìm thấy kết quả." - notice_bad_request: "Yêu cầu không hợp lệ." + notice_no_principals_found: "Không tìm thấy kết quả nào." + notice_bad_request: "Yêu cầu xấu." notice_not_authorized: "Bạn không được phép truy cập trang này." - notice_not_authorized_archived_project: "Dự án bạn đang cố gắng truy cập đã bị lưu trữ." - notice_requires_enterprise_token: "Enterprise token missing or doesn't allow access to this page." - notice_password_confirmation_failed: "The entered password is not correct." - notice_principals_found_multiple: "Có %{number} kết quả được tìm thấy. \n Nhấn Tab để tập trung vào kết quả đầu tiên." - notice_principals_found_single: "Có một kết quả. \n Nhấn Tab để tập trung vào nó." - notice_parent_item_not_found: "Parent item not found." + notice_not_authorized_archived_project: "Dự án bạn đang cố truy cập đã được lưu trữ." + notice_requires_enterprise_token: "Mã thông báo doanh nghiệp bị thiếu hoặc không cho phép truy cập vào trang này." + notice_password_confirmation_failed: "Mật khẩu đã nhập không chính xác." + notice_principals_found_multiple: "Có %{number} kết quả được tìm thấy. \n Tab để tập trung vào kết quả đầu tiên." + notice_principals_found_single: "Có một kết quả. \n Tab để tập trung vào nó." + notice_parent_item_not_found: "Không tìm thấy mục gốc." notice_project_not_deleted: "Dự án không bị xóa." - notice_project_not_found: "Không tìm thấy dự án." + notice_project_not_found: "Dự án không được tìm thấy." notice_successful_connection: "Kết nối thành công." - notice_successful_create: "Tạo thành công." + notice_successful_create: "Sáng tạo thành công." notice_successful_delete: "Xóa thành công." - notice_successful_cancel: "Successful cancellation." + notice_successful_cancel: "Hủy thành công." notice_successful_update: "Cập nhật thành công." - notice_unsuccessful_create: "Creation failed." - notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" - notice_unsuccessful_update: "Update failed." - notice_unsuccessful_update_with_reason: "Update failed: %{reason}" + notice_unsuccessful_create: "Tạo không thành công." + notice_unsuccessful_create_with_reason: "Tạo không thành công: %{reason}" + notice_unsuccessful_update: "Cập nhật không thành công." + notice_unsuccessful_update_with_reason: "Cập nhật không thành công: %{reason}" notice_successful_update_custom_fields_added_to_project: | Cập nhật thành công. Các trường tùy chỉnh của các loại đã kích hoạt sẽ tự động được kích hoạt trên biểu mẫu gói công việc. Xem thêm. - notice_to_many_principals_to_display: "Có quá nhiều kết quả.\nThu hẹp tìm kiếm bằng cách gõ tên của thành viên (hoặc nhóm) mới." - notice_user_missing_authentication_method: Người dùng chưa chọn mật khẩu hoặc phương thức đăng nhập khác. - notice_user_invitation_resent: Một lời mời đã được gửi đến %{email}. - present_access_key_value: "Mã truy cập của bạn %{key_name} là: %{value}" + notice_to_many_principals_to_display: "Có quá nhiều kết quả.\nThu hẹp tìm kiếm bằng cách nhập tên của thành viên (hoặc nhóm) mới." + notice_user_missing_authentication_method: Người dùng vẫn chưa chọn mật khẩu hoặc cách khác để đăng nhập. + notice_user_invitation_resent: Lời mời đã được gửi tới %{email}. + present_access_key_value: "%{key_name} của bạn là: %{value}" notice_automatic_set_of_standard_type: "Đặt loại tiêu chuẩn tự động." - notice_logged_out: "Bạn đã được đăng xuất." - notice_wont_delete_auth_source: Kết nối LDAP không thể bị xóa miễn là vẫn có người dùng đang sử dụng nó. + notice_logged_out: "Bạn đã đăng xuất." + notice_wont_delete_auth_source: Không thể xóa kết nối LDAP miễn là vẫn còn người dùng sử dụng nó. notice_project_cannot_update_custom_fields: "Bạn không thể cập nhật các mục tùy chỉnh có sẵn của dự án. Dự án không hợp lệ: %{errors}" notice_attachment_migration_wiki_page: > Trang này được tạo ra tự động trong quá trình Cập Nhật của OpenProject. Nó chứa tất cả các tập tin đính kèm trước đó liên kết với %{container_type} "%{container_name}". @@ -3950,354 +3948,354 @@ vi: format: "%n %u" units: byte: - other: "Bytes" + other: "Byte" gb: "GB" kb: "kB" mb: "MB" - tb: "TB" + tb: "bệnh lao" onboarding: - heading_getting_started: "Có cái nhìn tổng quan" - text_getting_started_description: "Nhận cái nhìn tổng quan nhanh về quản lý dự án và cộng tác nhóm với OpenProject. Bạn có thể khởi động lại video này từ menu trợ giúp." + heading_getting_started: "Nhận một cái nhìn tổng quan" + text_getting_started_description: "Nhận tổng quan nhanh về quản lý dự án và cộng tác nhóm với OpenProject. Bạn có thể khởi động lại video này từ menu trợ giúp." welcome: "Chào mừng đến với %{app_title}" select_language: "Vui lòng chọn ngôn ngữ của bạn" - permission_add_work_package_comments: "Add comments" + permission_add_work_package_comments: "Thêm nhận xét" permission_add_work_packages: "Thêm mới Gói công việc" permission_add_messages: "Đăng tin nhắn" permission_add_project: "Tạo dự án" - permission_add_portfolios: "Create portfolios" - permission_add_programs: "Create programs" + permission_add_portfolios: "Tạo danh mục đầu tư" + permission_add_programs: "Tạo chương trình" permission_add_work_package_attachments: "Tệp đính kèm" - permission_add_work_package_attachments_explanation: "Cho phép thêm tệp đính kèm mà không cần quyền chỉnh sửa gói công việc" - permission_add_internal_comments: "Write internal comments" + permission_add_work_package_attachments_explanation: "Cho phép thêm tệp đính kèm mà không có quyền Chỉnh sửa gói công việc" + permission_add_internal_comments: "Viết bình luận nội bộ" permission_archive_project: "Lưu trữ dự án" permission_create_user: "Tạo người dùng" permission_manage_user: "Chỉnh sửa người dùng" - permission_manage_placeholder_user: "Tạo, chỉnh sửa và xóa người dùng giả lập" + permission_manage_placeholder_user: "Tạo, chỉnh sửa và xóa người dùng giữ chỗ" permission_add_subprojects: "Tạo Dự án con" permission_add_work_package_watchers: "Thêm người theo dõi" - permission_assign_versions: "Gán phiên bản" - permission_browse_repository: "Quyền truy cập chỉ đọc vào kho (duyệt và kiểm tra)" + permission_assign_versions: "Chỉ định phiên bản" + permission_browse_repository: "Quyền truy cập chỉ đọc vào kho lưu trữ (duyệt và kiểm tra)" permission_change_wiki_parent_page: "Thay đổi trang wiki cha" permission_change_work_package_status: "Thay đổi trạng thái gói công việc" - permission_change_work_package_status_explanation: "Cho phép thay đổi trạng thái mà không cần quyền chỉnh sửa gói công việc" + permission_change_work_package_status_explanation: "Cho phép thay đổi trạng thái mà không có quyền Chỉnh sửa gói công việc" permission_comment_news: "Chú thích vào tin mới" - permission_commit_access: "Quyền đọc/ghi vào kho (cam kết)" + permission_commit_access: "Quyền truy cập đọc/ghi vào kho lưu trữ (cam kết)" permission_copy_projects: "Sao chép dự án" - permission_copy_projects_explanation: "In template projects, this permission has a secondary function, it allows the creation of new projects derived from the template." - permission_copy_work_packages: "Duplicate work packages" + permission_copy_projects_explanation: "Trong các dự án mẫu, quyền này có chức năng phụ, nó cho phép tạo các dự án mới bắt nguồn từ mẫu." + permission_copy_work_packages: "Gói công việc trùng lặp" permission_create_backup: "Tạo bản sao lưu" permission_delete_work_package_watchers: "Xóa người xem" permission_delete_work_packages: "Xóa gói công việc" permission_delete_messages: "Xóa tin nhắn" permission_delete_own_messages: "Xóa tin nhắn của tôi" permission_delete_reportings: "Xóa các báo cáo" - permission_delete_timelines: "Xóa mốc thời gian" - permission_delete_wiki_pages: "Xóa trang wiki" + permission_delete_timelines: "Xóa dòng thời gian" + permission_delete_wiki_pages: "Xóa các trang wiki" permission_delete_wiki_pages_attachments: "Xóa tệp đính kèm" - permission_edit_work_package_comments: "Moderate comments" - permission_edit_work_package_comments_explanation: "Caution: Users with this permission are able to edit anyone's comment." + permission_edit_work_package_comments: "Kiểm duyệt bình luận" + permission_edit_work_package_comments_explanation: "Thận trọng: Người dùng có quyền này có thể chỉnh sửa nhận xét của bất kỳ ai." permission_edit_work_packages: "Chỉnh sửa gói công việc" permission_edit_messages: "Chỉnh sửa tin nhắn" - permission_edit_own_internal_comments: "Edit own internal comments" - permission_edit_own_work_package_comments: "Edit own comments" - permission_edit_own_messages: "Chỉnh sửa tin nhắn của riêng bạn" + permission_edit_own_internal_comments: "Chỉnh sửa nhận xét nội bộ của riêng mình" + permission_edit_own_work_package_comments: "Chỉnh sửa nhận xét của chính mình" + permission_edit_own_messages: "Chỉnh sửa tin nhắn riêng" permission_edit_own_time_entries: "Chỉnh sửa nhật ký thời gian của riêng bạn" - permission_edit_others_internal_comments: "Moderate internal comments" - permission_edit_others_internal_comments_explanation: "Caution: Users with this permission are able to edit other users' internal comments." + permission_edit_others_internal_comments: "Bình luận nội bộ vừa phải" + permission_edit_others_internal_comments_explanation: "Thận trọng: Người dùng có quyền này có thể chỉnh sửa nhận xét nội bộ của người dùng khác." permission_edit_project: "Chỉnh sửa dự án" permission_edit_project_attributes: "Chỉnh sửa thuộc tính dự án" - permission_edit_project_phases: "Edit project phases" + permission_edit_project_phases: "Chỉnh sửa các giai đoạn dự án" permission_edit_reportings: "Chỉnh sửa báo cáo" - permission_edit_time_entries: "Chỉnh sửa nhật ký thời gian của người khác" - permission_edit_timelines: "Chỉnh sửa mốc thời gian" + permission_edit_time_entries: "Chỉnh sửa nhật ký thời gian cho người dùng khác" + permission_edit_timelines: "Chỉnh sửa dòng thời gian" permission_edit_wiki_pages: "Chỉnh sửa trang wiki" permission_export_work_packages: "Xuất gói công việc" - permission_export_projects: "Export projects" + permission_export_projects: "Dự án xuất khẩu" permission_export_wiki_pages: "Xuất trang wiki" - permission_invite_members_by_email: "Invite members by email" + permission_invite_members_by_email: "Mời thành viên qua email" permission_invite_members_by_email_explanation: > - Allows users to invite new members by email. Invited users will receive an email with a link to set their password and activate their account. Depends on the permission to manage members + Cho phép người dùng mời thành viên mới qua email. Người dùng được mời sẽ nhận được email có liên kết để đặt mật khẩu và kích hoạt tài khoản của họ. Phụ thuộc vào quyền quản lý thành viên permission_list_attachments: "Liệt kê tệp đính kèm" - permission_log_own_time: "Ghi nhật ký thời gian của riêng bạn" - permission_log_time: "Ghi nhật ký thời gian cho người khác" + permission_log_own_time: "Đăng nhập thời gian riêng" + permission_log_time: "Đăng nhập thời gian cho người dùng khác" permission_manage_forums: "Quản lý diễn đàn" permission_manage_categories: "Quản lý danh mục gói công việc" - permission_manage_dashboards: "Quản lý bảng điều khiển" - permission_manage_work_package_relations: "Quản lý mối quan hệ gói công việc" + permission_manage_dashboards: "Quản lý trang tổng quan" + permission_manage_work_package_relations: "Quản lý quan hệ gói công việc" permission_manage_members: "Quản lý thành viên" permission_manage_news: "Quản lý tin tức" permission_manage_project_activities: "Quản lý hoạt động dự án" permission_manage_public_queries: "Quản lý truy vấn công cộng" - permission_manage_repository: "Quản lý kho" + permission_manage_repository: "Quản lý kho lưu trữ" permission_manage_subtasks: "Quản lý phân cấp gói công việc" permission_manage_versions: "Quản lý phiên bản" permission_manage_wiki: "Quản lý wiki" permission_manage_wiki_menu: "Quản lý menu wiki" permission_move_work_packages: "Di chuyển gói công việc" - permission_protect_wiki_pages: "Bảo vệ trang wiki" - permission_rename_wiki_pages: "Đổi tên trang wiki" + permission_protect_wiki_pages: "Bảo vệ các trang wiki" + permission_rename_wiki_pages: "Đổi tên các trang wiki" permission_save_queries: "Lưu các chế độ xem" permission_search_project: "Tìm kiếm dự án" permission_select_custom_fields: "Chọn trường tùy chỉnh" permission_select_project_custom_fields: "Chọn thuộc tính dự án" - permission_select_project_phases: "Select project phases" - permission_select_project_phases_explanation: "Activate/Deactivate the phases in a project. Enables the user to select the life cycle appropriate for the project as inactive phases will not be visible in the project overview page nor the project list." - permission_select_project_modules: "Chọn các mô-đun dự án" + permission_select_project_phases: "Chọn giai đoạn dự án" + permission_select_project_phases_explanation: "Kích hoạt/hủy kích hoạt các giai đoạn trong dự án. Cho phép người dùng chọn vòng đời phù hợp cho dự án vì các giai đoạn không hoạt động sẽ không hiển thị trên trang tổng quan dự án cũng như danh sách dự án." + permission_select_project_modules: "Chọn mô-đun dự án" permission_share_work_packages: "Chia sẻ gói công việc" permission_manage_types: "Chọn loại" - permission_manage_own_reminders: "Create own reminders" - permission_view_all_principals: "View all users and groups" + permission_manage_own_reminders: "Tạo lời nhắc riêng" + permission_view_all_principals: "Xem tất cả người dùng và nhóm" permission_view_all_principals_explanation: > - Allows users to see all users and groups in the system, even if they are not members of any joined projects or groups. + Cho phép người dùng xem tất cả người dùng và nhóm trong hệ thống, ngay cả khi họ không phải là thành viên của bất kỳ dự án hoặc nhóm nào đã tham gia. permission_view_project: "Xem dự án" - permission_view_changesets: "Xem các sửa đổi kho trong OpenProject" - permission_view_internal_comments: "View internal comments" - permission_view_commit_author_statistics: "Xem thống kê tác giả cam kết" - permission_view_dashboards: "Xem bảng điều khiển" + permission_view_changesets: "Xem các bản sửa đổi kho lưu trữ trong OpenProject" + permission_view_internal_comments: "Xem bình luận nội bộ" + permission_view_commit_author_statistics: "Xem số liệu thống kê về tác giả cam kết" + permission_view_dashboards: "Xem trang tổng quan" permission_view_work_package_watchers: "Xem danh sách người theo dõi" - permission_view_work_packages: "Xem gói công việc" + permission_view_work_packages: "Xem các gói công việc" permission_view_messages: "Xem tin nhắn" permission_view_news: "Xem tin tức" permission_view_members: "Xem thành viên" permission_view_reportings: "Xem báo cáo" permission_view_shared_work_packages: "Xem chia sẻ gói công việc" - permission_view_time_entries: "Xem thời gian đã chi tiêu" - permission_view_timelines: "Xem mốc thời gian" - permission_view_user_email: "View users' mail addresses" - permission_view_wiki_edits: "Xem lịch sử chỉnh sửa wiki" + permission_view_time_entries: "Xem thời gian đã sử dụng" + permission_view_timelines: "Xem dòng thời gian" + permission_view_user_email: "Xem địa chỉ thư của người dùng" + permission_view_wiki_edits: "Xem lịch sử wiki" permission_view_wiki_pages: "Xem wiki" - permission_work_package_assigned: "Trở thành người được chỉ định/chịu trách nhiệm" - permission_work_package_assigned_explanation: "Gói công việc có thể được gán cho người dùng và nhóm sở hữu vai trò này trong dự án tương ứng" + permission_work_package_assigned: "Trở thành người được giao/chịu trách nhiệm" + permission_work_package_assigned_explanation: "Các gói công việc có thể được chỉ định cho người dùng và nhóm có vai trò này trong dự án tương ứng" permission_view_project_activity: "Xem hoạt động dự án" permission_view_project_attributes: "Xem thuộc tính dự án" - permission_view_project_phases: "View project phases" - permission_save_bcf_queries: "Lưu các truy vấn BCF" - permission_manage_public_bcf_queries: "Quản lý các truy vấn BCF công khai" + permission_view_project_phases: "Xem các giai đoạn dự án" + permission_save_bcf_queries: "Lưu truy vấn BCF" + permission_manage_public_bcf_queries: "Quản lý truy vấn BCF công khai" permission_edit_attribute_help_texts: "Chỉnh sửa văn bản trợ giúp thuộc tính" - permission_manage_public_project_queries: "Quản lý danh sách dự án công khai" + permission_manage_public_project_queries: "Quản lý danh sách dự án công" permission_view_project_query: "Xem truy vấn dự án" permission_edit_project_query: "Chỉnh sửa truy vấn dự án" placeholders: default: "-" portfolio: count: - zero: "0 portfolios" - one: "1 portfolio" - other: "%{count} portfolios" + zero: "0 danh mục đầu tư" + one: "1 danh mục đầu tư" + other: "%{count} danh mục đầu tư" program: count: - zero: "0 programs" - one: "1 program" - other: "%{count} programs" + zero: "0 chương trình" + one: "1 chương trình" + other: "%{count} chương trình" project: archive: - title: "Archive project" + title: "Lưu trữ dự án" are_you_sure: "Bạn có chắc bạn muốn lưu trữ các dự án '%{name}'?" archived: "Đã lưu" count: - zero: "0 projects" - one: "một dự án" + zero: "0 dự án" + one: "1 dự án" other: "%{count} dự án" destroy: title: "Xóa dự án" - heading: "Permanently delete this project?" + heading: "Xóa vĩnh viễn dự án này?" confirmation_message_for_subprojects_html: zero: > - You are about to permanently delete all data relating to project %{name}. + Bạn sắp xóa vĩnh viễn tất cả dữ liệu liên quan đến dự án %{name}. one: > - You are about to permanently delete all data relating to project %{name} and this subproject: + Bạn sắp xóa vĩnh viễn tất cả dữ liệu liên quan đến dự án %{name} và tiểu dự án này: other: > - You are about to permanently delete all data relating to project %{name} and these subprojects: + Bạn sắp xóa vĩnh viễn tất cả dữ liệu liên quan đến dự án %{name} và các dự án con này: filters: - project_phase: "Project phase: %{phase}" - project_phase_any: "Project phase: Any" - project_phase_gate: "Project phase gate: %{gate}" + project_phase: "Giai đoạn dự án: %{phase}" + project_phase_any: "Giai đoạn dự án: Bất kỳ" + project_phase_gate: "Cổng giai đoạn dự án: %{gate}" identifier: - warning_one: Các thành viên của dự án sẽ phải di chuyển các kho của dự án. + warning_one: Các thành viên của dự án sẽ phải di dời các kho của dự án. warning_two: Các liên kết hiện có tới dự án sẽ không còn hoạt động. title: Thay đổi mã định danh của dự án - not_available: "Project N/A" + not_available: "Dự án N/A" template: - copying_title: "Applying template" + copying_title: "Áp dụng mẫu" copying: > - Dự án của bạn đang được tạo từ dự án mẫu đã chọn. Bạn sẽ nhận được thông báo qua email ngay khi dự án có sẵn. + Dự án của bạn đang được tạo từ dự án mẫu đã chọn. Bạn sẽ được thông báo qua thư ngay khi dự án có sẵn. use_template: "Sử dụng mẫu" - make_template: "Đặt thành mẫu" + make_template: "Đặt làm mẫu" remove_from_templates: "Xóa khỏi mẫu" - project_module_activity: "Hoạt động" - project_module_forums: "Diễn đàn" + project_module_activity: "hoạt động" + project_module_forums: "diễn đàn" project_module_work_package_tracking: "Work Packages" - project_module_news: "Tin tức" - project_module_repository: "Kho lưu trữ" - project_module_wiki: "Wiki" + project_module_news: "tin tức" + project_module_repository: "kho lưu trữ" + project_module_wiki: "wiki" permission_header_for_project_module_work_package_tracking: "Gói công việc và biểu đồ Gantt" query: attribute_and_direction: "%{attribute} (%{direction})" #possible query parameters (e.g. issue queries), #which are not attributes of an AR-Model. query_fields: - active_or_archived: "Hoạt động hoặc đã lưu trữ" - assigned_to_role: "Vai trò của người được chỉ định" + active_or_archived: "Đang hoạt động hoặc được lưu trữ" + assigned_to_role: "Vai trò của người được ủy quyền" assignee_or_group: "Người chuyển hoặc thuộc nhóm" - member_of_group: "Nhóm của người được chỉ định" + member_of_group: "Nhóm người được ủy quyền" name_or_identifier: "Tên hoặc mã định danh" - only_subproject_id: "Chỉ dự án con" - shared_with_user: "Chia sẻ với người dùng" - shared_with_me: "Chia sẻ với tôi" - subproject_id: "Bao gồm dự án con" + only_subproject_id: "Chỉ có tiểu dự án" + shared_with_user: "Được chia sẻ với người dùng" + shared_with_me: "Đã chia sẻ với tôi" + subproject_id: "Bao gồm tiểu dự án" repositories: at_identifier: "tại %{identifier}" - atom_revision_feed: "Dòng thời gian phiên bản Atom" - autofetch_information: "Chọn tùy chọn này nếu bạn muốn các kho được cập nhật tự động khi truy cập trang mô-đun kho.\nĐiều này bao gồm việc lấy các cam kết từ kho và làm mới dung lượng đĩa cần thiết." + atom_revision_feed: "Nguồn cấp dữ liệu sửa đổi Atom" + autofetch_information: "Chọn tùy chọn này nếu bạn muốn các kho lưu trữ được cập nhật tự động khi truy cập trang mô-đun kho lưu trữ.\nĐiều này bao gồm việc truy xuất các cam kết từ kho lưu trữ và làm mới bộ nhớ đĩa cần thiết." checkout: access: - readwrite: "Đọc + Ghi" + readwrite: "Đọc + Viết" read: "Chỉ đọc" - none: "Không có quyền truy cập, bạn chỉ có thể xem kho qua ứng dụng này." - access_permission: "Quyền của bạn trên kho này" - url: "URL để kiểm tra" - base_url_text: "URL cơ bản để sử dụng khi tạo URL kiểm tra (ví dụ: https://myserver.example.org/repos/).\nLưu ý: URL cơ bản chỉ được sử dụng để viết lại các URL kiểm tra trong các kho được quản lý. Các kho khác không bị thay đổi." + none: "Không có quyền truy cập thanh toán, bạn chỉ có thể xem kho lưu trữ thông qua ứng dụng này." + access_permission: "Quyền của bạn đối với kho lưu trữ này" + url: "URL thanh toán" + base_url_text: "URL cơ sở dùng để tạo URL thanh toán (ví dụ: https://myserver.example.org/repos/).\nLưu ý: URL cơ sở chỉ được sử dụng để viết lại URL thanh toán trong kho được quản lý. Các kho lưu trữ khác không bị thay đổi." default_instructions: git: |- - Dữ liệu trong kho này có thể được tải xuống máy tính của bạn bằng Git. - Vui lòng tham khảo tài liệu của Git nếu bạn cần thêm thông tin về quy trình kiểm tra và các khách hàng có sẵn. + Dữ liệu chứa trong kho lưu trữ này có thể được tải xuống máy tính của bạn bằng Git. + Vui lòng tham khảo tài liệu của Git nếu bạn cần thêm thông tin về quy trình thanh toán và các khách hàng hiện có. subversion: |- - Dữ liệu trong kho này có thể được tải xuống máy tính của bạn bằng Subversion. - Vui lòng tham khảo tài liệu của Subversion nếu bạn cần thêm thông tin về quy trình kiểm tra và các khách hàng có sẵn. - enable_instructions_text: "Hiển thị hướng dẫn kiểm tra được định nghĩa dưới đây trên tất cả các trang liên quan đến kho." - instructions: "Hướng dẫn kiểm tra" - show_instructions: "Hiển thị hướng dẫn kiểm tra" - text_instructions: "Văn bản này được hiển thị cùng với URL kiểm tra để hướng dẫn cách kiểm tra kho." - not_available: "Hướng dẫn kiểm tra không được định nghĩa cho kho này. Hãy yêu cầu quản trị viên của bạn bật chúng cho kho này trong cài đặt hệ thống." - create_managed_delay: "Xin lưu ý: Kho được quản lý, nó được tạo một cách bất đồng bộ trên đĩa và sẽ sớm có sẵn." - create_successful: "Kho đã được đăng ký." + Dữ liệu chứa trong kho lưu trữ này có thể được tải xuống máy tính của bạn bằng Subversion. + Vui lòng tham khảo tài liệu của Subversion nếu bạn cần thêm thông tin về quy trình thanh toán và các khách hàng hiện có. + enable_instructions_text: "Hiển thị hướng dẫn thanh toán được xác định bên dưới trên tất cả các trang liên quan đến kho lưu trữ." + instructions: "Hướng dẫn thanh toán" + show_instructions: "Hiển thị hướng dẫn thanh toán" + text_instructions: "Văn bản này được hiển thị cùng với URL thanh toán để được hướng dẫn cách kiểm tra kho lưu trữ." + not_available: "Hướng dẫn thanh toán không được xác định cho kho lưu trữ này. Hãy yêu cầu quản trị viên của bạn kích hoạt chúng cho kho lưu trữ này trong cài đặt hệ thống." + create_managed_delay: "Xin lưu ý: Kho lưu trữ được quản lý, nó được tạo không đồng bộ trên đĩa và sẽ sớm ra mắt." + create_successful: "Kho lưu trữ đã được đăng ký." delete_sucessful: "Kho lưu trữ đã bị xóa." destroy: - confirmation: "Nếu bạn tiếp tục, điều này sẽ xóa vĩnh viễn kho được quản lý." - info: "Việc xóa kho là hành động không thể khôi phục." - info_not_managed: "Lưu ý: Điều này KHÔNG xóa nội dung của kho này, vì nó không được quản lý bởi OpenProject." + confirmation: "Nếu bạn tiếp tục, thao tác này sẽ xóa vĩnh viễn kho lưu trữ được quản lý." + info: "Xóa kho lưu trữ là một hành động không thể đảo ngược." + info_not_managed: "Lưu ý: Thao tác này sẽ KHÔNG xóa nội dung của kho lưu trữ này vì nó không được OpenProject quản lý." managed_path_note: "Thư mục sau sẽ bị xóa: %{path}" - repository_verification: "Nhập mã định danh của dự án %{identifier} để xác minh việc xóa kho của nó." + repository_verification: "Nhập mã định danh của dự án %{identifier} để xác minh việc xóa kho lưu trữ của dự án." subtitle: "Bạn có thực sự muốn xóa %{repository_type} của dự án %{project_name} không?" - subtitle_not_managed: "Bạn có thực sự muốn xóa %{repository_type} liên kết %{url} khỏi dự án %{project_name} không?" + subtitle_not_managed: "Bạn có thực sự muốn xóa %{repository_type} %{url} được liên kết khỏi dự án %{project_name} không?" title: "Xóa %{repository_type}" - title_not_managed: "Xóa %{repository_type} liên kết?" + title_not_managed: "Xóa %{repository_type} được liên kết?" errors: - build_failed: "Không thể tạo kho với cấu hình đã chọn. %{reason}" - managed_delete: "Không thể xóa kho được quản lý." - managed_delete_local: "Không thể xóa kho cục bộ trên hệ thống tập tin tại '%{path}': %{error_message}" - empty_repository: "Kho tồn tại nhưng trống rỗng. Nó chưa chứa bất kỳ phiên bản nào." - exists_on_filesystem: "Thư mục kho đã tồn tại trên hệ thống tập tin." - filesystem_access_failed: "Đã xảy ra lỗi khi truy cập kho trên hệ thống tập tin: %{message}" - not_manageable: "Nhà cung cấp kho này không thể được quản lý bởi OpenProject." - path_permission_failed: "Đã xảy ra lỗi khi cố gắng tạo đường dẫn sau: %{path}. Vui lòng đảm bảo rằng OpenProject có thể ghi vào thư mục đó." - unauthorized: "Bạn không được phép truy cập kho hoặc thông tin xác thực không hợp lệ." - unavailable: "Kho không khả dụng." - exception_title: "Không thể truy cập kho: %{message}" - disabled_or_unknown_type: "Loại đã chọn %{type} bị tắt hoặc không còn khả dụng cho nhà cung cấp SCM %{vendor}." - disabled_or_unknown_vendor: "Nhà cung cấp SCM %{vendor} bị tắt hoặc không còn khả dụng." - remote_call_failed: "Gọi đến remote được quản lý thất bại với thông điệp '%{message}' (Mã: %{code})" - remote_invalid_response: "Nhận phản hồi không hợp lệ từ remote được quản lý." - remote_save_failed: "Không thể lưu kho với các tham số lấy từ remote." + build_failed: "Không thể tạo kho lưu trữ với cấu hình đã chọn. %{reason}" + managed_delete: "Không thể xóa kho lưu trữ được quản lý." + managed_delete_local: "Không thể xóa kho lưu trữ cục bộ trên hệ thống tệp tại '%{path}': %{error_message}" + empty_repository: "Kho lưu trữ tồn tại nhưng trống rỗng. Nó chưa chứa bất kỳ sửa đổi nào." + exists_on_filesystem: "Thư mục kho lưu trữ đã tồn tại trong hệ thống tập tin." + filesystem_access_failed: "Đã xảy ra lỗi khi truy cập kho lưu trữ trong hệ thống tệp: %{message}" + not_manageable: "OpenProject không thể quản lý nhà cung cấp kho lưu trữ này." + path_permission_failed: "Đã xảy ra lỗi khi tạo đường dẫn sau: %{path}. Hãy đảm bảo rằng OpenProject có thể ghi vào thư mục đó." + unauthorized: "Bạn không được phép truy cập vào kho lưu trữ hoặc thông tin xác thực không hợp lệ." + unavailable: "Kho lưu trữ không có sẵn." + exception_title: "Không thể truy cập kho lưu trữ: %{message}" + disabled_or_unknown_type: "Loại đã chọn %{type} bị vô hiệu hóa hoặc không còn khả dụng cho nhà cung cấp SCM %{vendor}." + disabled_or_unknown_vendor: "Nhà cung cấp SCM %{vendor} đã bị vô hiệu hóa hoặc không còn khả dụng." + remote_call_failed: "Gọi điều khiển từ xa được quản lý không thành công với thông báo '%{message}' (Mã: %{code})" + remote_invalid_response: "Đã nhận được phản hồi không hợp lệ từ điều khiển từ xa được quản lý." + remote_save_failed: "Không thể lưu kho lưu trữ với các tham số được truy xuất từ ​​​​điều khiển từ xa." git: instructions: - managed_url: "Đây là URL của kho Git được quản lý (cục bộ)." + managed_url: "Đây là URL của kho lưu trữ Git (cục bộ) được quản lý." path: >- Chỉ định đường dẫn đến repository Git của bạn (ví dụ, %{example_path}). Bạn cũng có thể sử dụng các remote repositories mà được nhân bản với một bản sao cục bộ bằng cách sử dụng một giá trị bắt đầu với http(s) :// hoặc file://. path_encoding: "Ghi đè mã hóa đường dẫn Git (Mặc định: UTF-8)" - local_title: "Liên kết kho Git cục bộ hiện có" - local_url: "URL cục bộ" - local_introduction: "Nếu bạn có một kho Git cục bộ hiện có, bạn có thể liên kết nó với OpenProject để truy cập từ bên trong ứng dụng." - managed_introduction: "Để OpenProject tự động tạo và tích hợp kho Git cục bộ." - managed_title: "Kho Git tích hợp vào OpenProject" + local_title: "Liên kết kho lưu trữ Git cục bộ hiện có" + local_url: "URL địa phương" + local_introduction: "Nếu bạn có kho lưu trữ Git cục bộ hiện có, bạn có thể liên kết nó với OpenProject để truy cập nó từ bên trong ứng dụng." + managed_introduction: "Hãy để OpenProject tự động tạo và tích hợp kho lưu trữ Git cục bộ." + managed_title: "Kho lưu trữ Git được tích hợp vào OpenProject" managed_url: "URL được quản lý" path: "Đường dẫn đến kho Git" path_encoding: "Mã hóa đường dẫn" - go_to_revision: "Đi đến phiên bản" - managed_remote: "Các kho được quản lý cho nhà cung cấp này được xử lý từ xa." - managed_remote_note: "Thông tin về URL và đường dẫn của kho này không có sẵn trước khi nó được tạo." + go_to_revision: "Đi đến bản sửa đổi" + managed_remote: "Các kho lưu trữ được quản lý cho nhà cung cấp này được xử lý từ xa." + managed_remote_note: "Thông tin về URL và đường dẫn của kho lưu trữ này không có sẵn trước khi tạo." managed_url: "URL được quản lý" settings: - automatic_managed_repos_disabled: "Tắt việc tạo tự động" - automatic_managed_repos: "Tạo tự động các kho được quản lý" - automatic_managed_repos_text: "Bằng cách thiết lập một nhà cung cấp ở đây, các dự án mới được tạo sẽ tự động nhận một kho được quản lý của nhà cung cấp này." - scm_vendor: "Hệ thống quản lý mã nguồn" - scm_type: "Loại kho" + automatic_managed_repos_disabled: "Tắt tính năng tạo tự động" + automatic_managed_repos: "Tự động tạo các kho lưu trữ được quản lý" + automatic_managed_repos_text: "Bằng cách đặt nhà cung cấp ở đây, các dự án mới tạo sẽ tự động nhận được kho lưu trữ được quản lý của nhà cung cấp này." + scm_vendor: "Hệ thống quản lý kiểm soát nguồn" + scm_type: "Loại kho lưu trữ" scm_types: - local: "Liên kết kho cục bộ hiện có" - existing: "Liên kết kho hiện có" - managed: "Tạo kho mới trong OpenProject" + local: "Liên kết kho lưu trữ cục bộ hiện có" + existing: "Liên kết kho lưu trữ hiện có" + managed: "Tạo kho lưu trữ mới trong OpenProject" storage: - not_available: "Tiêu thụ dung lượng đĩa không có sẵn cho kho này." - update_timeout: "Giữ thông tin dung lượng đĩa cần thiết cho kho trong N phút.\nVì việc tính toán dung lượng đĩa cần thiết cho kho có thể tốn kém, hãy tăng giá trị này để giảm tác động đến hiệu suất." - oauth_application_details: "Giá trị bí mật của khách hàng sẽ không còn khả dụng sau khi bạn đóng cửa sổ này. Vui lòng sao chép các giá trị này vào cài đặt tích hợp OpenProject của Nextcloud:" - oauth_application_details_link_text: "Đi đến trang cài đặt" - setup_documentation_details: "Nếu bạn cần trợ giúp để cấu hình một lưu trữ tệp mới, vui lòng kiểm tra tài liệu: " - setup_documentation_details_link_text: "File storages setup" - show_warning_details: "Để sử dụng lưu trữ tệp này, hãy nhớ kích hoạt mô-đun và lưu trữ cụ thể trong cài đặt dự án của từng dự án mong muốn." + not_available: "Mức tiêu thụ dung lượng ổ đĩa không có sẵn cho kho lưu trữ này." + update_timeout: "Giữ thông tin dung lượng ổ đĩa cần thiết cuối cùng cho kho lưu trữ trong N phút.\nVì việc tính dung lượng đĩa cần thiết của kho lưu trữ có thể tốn kém nên hãy tăng giá trị này để giảm tác động đến hiệu suất." + oauth_application_details: "Giá trị bí mật của máy khách sẽ không thể truy cập lại được sau khi bạn đóng cửa sổ này. Vui lòng sao chép các giá trị này vào cài đặt Tích hợp Nextcloud OpenProject:" + oauth_application_details_link_text: "Đi tới trang cài đặt" + setup_documentation_details: "Nếu bạn cần trợ giúp định cấu hình bộ lưu trữ tệp mới, vui lòng kiểm tra tài liệu:" + setup_documentation_details_link_text: "Thiết lập kho lưu trữ tập tin" + show_warning_details: "Để sử dụng bộ lưu trữ tệp này, hãy nhớ kích hoạt mô-đun và bộ lưu trữ cụ thể trong cài đặt dự án của từng dự án mong muốn." subversion: - existing_title: "Kho Subversion hiện có" - existing_introduction: "Nếu bạn có một kho Subversion hiện có, bạn có thể liên kết nó với OpenProject để truy cập từ bên trong ứng dụng." - existing_url: "URL hiện có" + existing_title: "Kho lưu trữ Subversion hiện có" + existing_introduction: "Nếu bạn có kho lưu trữ Subversion hiện có, bạn có thể liên kết nó với OpenProject để truy cập nó từ bên trong ứng dụng." + existing_url: "URL hiện tại" instructions: - managed_url: "Đây là URL của kho Subversion được quản lý (cục bộ)." - url: "Nhập URL của kho. Điều này có thể nhắm đến một kho cục bộ (bắt đầu với %{local_proto} ), hoặc một kho từ xa.\nCác sơ đồ URL sau đây được hỗ trợ:" - managed_title: "Kho Subversion tích hợp vào OpenProject" - managed_introduction: "Để OpenProject tự động tạo và tích hợp kho Subversion cục bộ." + managed_url: "Đây là URL của kho lưu trữ Subversion (cục bộ) được quản lý." + url: "Nhập URL kho lưu trữ. Điều này có thể nhắm mục tiêu kho lưu trữ cục bộ (bắt đầu bằng %{local_proto} ) hoặc kho lưu trữ từ xa.\nCác lược đồ URL sau được hỗ trợ:" + managed_title: "Kho lưu trữ Subversion được tích hợp vào OpenProject" + managed_introduction: "Hãy để OpenProject tự động tạo và tích hợp kho lưu trữ Subversion cục bộ." managed_url: "URL được quản lý" - password: "Mật khẩu kho" - username: "Tên người dùng kho" - truncated: "Xin lỗi, chúng tôi đã phải cắt ngắn thư mục này xuống %{limit} tệp. %{truncated} mục đã bị bỏ qua khỏi danh sách." - named_repository: "Kho %{vendor_name}" - update_settings_successful: "Cài đặt đã được lưu thành công." - url: "URL đến kho" + password: "Mật khẩu kho lưu trữ" + username: "Tên người dùng kho lưu trữ" + truncated: "Rất tiếc, chúng tôi đã phải cắt bớt thư mục này thành các tệp %{limit}. %{truncated} mục nhập đã bị bỏ qua khỏi danh sách." + named_repository: "kho lưu trữ %{vendor_name}" + update_settings_successful: "Các cài đặt đã được lưu thành công." + url: "URL tới kho lưu trữ" warnings: - cannot_annotate: "Tệp này không thể được chú thích." + cannot_annotate: "Tập tin này không thể được chú thích." scheduling: - manual: "set to Manual" - automatic: "set to Automatic" + manual: "đặt thành Thủ công" + automatic: "đặt thành Tự động" search_input_placeholder: "Tìm kiếm ..." - setting_allowed_link_protocols: "Allowed link protocols" + setting_allowed_link_protocols: "Giao thức liên kết được phép" setting_allowed_link_protocols_text_html: >- - Allow these protocols to be rendered as links in work package descriptions, long text fields and comments. For example, %{tel_code} or %{element_code}. Enter one protocol per line.
    Protocols %{http_code}, %{https_code}, and %{mailto_code} are always allowed. - setting_capture_external_links: "Capture external links" + Cho phép các giao thức này được hiển thị dưới dạng liên kết trong mô tả gói công việc, trường văn bản dài và nhận xét. Ví dụ: %{tel_code} hoặc %{element_code}. Nhập một giao thức trên mỗi dòng.
    Giao thức %{http_code}, %{https_code} và %{mailto_code} luôn được cho phép. + setting_capture_external_links: "Chụp các liên kết bên ngoài" setting_capture_external_links_text: > - When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. - setting_after_first_login_redirect_url: "First login redirect" + Khi tính năng này được kích hoạt, tất cả các liên kết ngoài trong văn bản định dạng sẽ được chuyển hướng qua một trang cảnh báo trước khi rời khỏi ứng dụng. Điều này giúp bảo vệ người dùng khỏi các trang web bên ngoài có thể chứa mã độc hại. + setting_after_first_login_redirect_url: "Chuyển hướng đăng nhập lần đầu" setting_after_first_login_redirect_url_text_html: > - Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page - setting_after_login_default_redirect_url: "After login redirect" + Đặt đường dẫn để chuyển hướng người dùng sau lần đăng nhập đầu tiên của họ. Nếu trống, hãy chuyển hướng đến trang chủ để xem chuyến tham quan giới thiệu.
    Ví dụ: /my/page + setting_after_login_default_redirect_url: "Sau khi đăng nhập chuyển hướng" setting_after_login_default_redirect_url_text_html: > - Set a default path to redirect users after login, if no back link was provided. Redirects to home page if not set.
    Example: /my/page - setting_apiv3_cors_title: "Cross-Origin Resource Sharing (CORS)" + Đặt đường dẫn mặc định để chuyển hướng người dùng sau khi đăng nhập, nếu không có liên kết ngược nào được cung cấp. Chuyển hướng đến trang chủ nếu không được thiết lập.
    Ví dụ: /my/page + setting_apiv3_cors_title: "Chia sẻ tài nguyên chéo nguồn gốc (CORS)" setting_apiv3_cors_enabled: "Kích hoạt CORS" - setting_apiv3_cors_origins: "Các nguồn gốc được phép của API V3 Cross-Origin Resource Sharing (CORS)" + setting_apiv3_cors_origins: "Nguồn gốc được phép chia sẻ tài nguyên chéo nguồn gốc (CORS) của API V3" setting_apiv3_cors_origins_text_html: > Nếu CORS được kích hoạt, đây là các nguồn gốc được phép truy cập API của OpenProject.
    Vui lòng kiểm tra Tài liệu về Header Origin để biết cách chỉ định các giá trị mong đợi. - setting_apiv3_write_readonly_attributes: "Quyền ghi vào thuộc tính chỉ đọc" + setting_apiv3_write_readonly_attributes: "Viết quyền truy cập vào các thuộc tính chỉ đọc" setting_apiv3_write_readonly_attributes_instructions: > - If enabled, the API will allow administrators to write static read-only attributes during creation, such as createdAt and author. + Nếu được bật, API sẽ cho phép quản trị viên ghi các thuộc tính tĩnh chỉ đọc trong quá trình tạo, chẳng hạn như createAt và tác giả. setting_apiv3_write_readonly_attributes_warning: > - This setting has a use-case for e.g., importing data, but allows administrators to impersonate the creation of items as other users. All creation requests are being logged however with the true author. + Cài đặt này có một trường hợp sử dụng chẳng hạn như nhập dữ liệu nhưng cho phép quản trị viên mạo danh việc tạo các mục như những người dùng khác. Tuy nhiên, tất cả các yêu cầu tạo đang được ghi lại với tác giả thực sự. setting_apiv3_write_readonly_attributes_additional: > - For more information on attributes and supported resources, please see the %{api_documentation_link}. + Để biết thêm thông tin về các thuộc tính và tài nguyên được hỗ trợ, vui lòng xem %{api_documentation_link}. setting_apiv3_max_page_size: "Kích thước trang API tối đa" setting_apiv3_max_page_size_instructions: > - Set the maximum page size the API will respond with. It will not be possible to perform API requests that return more values on a single page. + Đặt kích thước trang tối đa mà API sẽ phản hồi. Sẽ không thể thực hiện các yêu cầu API trả về nhiều giá trị hơn trên một trang. setting_apiv3_max_page_size_warning: > - Please only change this value if you are sure why you need it. Setting to a high value will result in significant performance impacts, while a value lower than the per page options will cause errors in paginated views. - setting_apiv3_docs: "Tài liệu" - setting_apiv3_docs_enabled: "Kích hoạt trang tài liệu" + Vui lòng chỉ thay đổi giá trị này nếu bạn chắc chắn tại sao mình cần nó. Việc đặt thành giá trị cao sẽ dẫn đến tác động đáng kể đến hiệu suất, trong khi giá trị thấp hơn tùy chọn trên mỗi trang sẽ gây ra lỗi trong chế độ xem phân trang. + setting_apiv3_docs: "tài liệu" + setting_apiv3_docs_enabled: "Bật trang tài liệu" setting_apiv3_docs_enabled_instructions_html: > Nếu trang tài liệu được kích hoạt, bạn có thể xem tài liệu APIv3 tương tác tại %{link}. setting_apiv3_docs_enabled_instructions_warning: > - Please be aware that enabling the API docs on a production system may expose sensitive information or result in accidental loss of data when not being careful. We recommend to only enable this setting for development purposes. + Xin lưu ý rằng việc bật tài liệu API trên hệ thống sản xuất có thể làm lộ thông tin nhạy cảm hoặc vô tình làm mất dữ liệu khi bạn không cẩn thận. Chúng tôi khuyên bạn chỉ nên bật cài đặt này cho mục đích phát triển. setting_attachment_whitelist: "Danh sách trắng tải lên tệp đính kèm" setting_email_delivery_method: "Cách gửi email" - setting_emails_salutation: "Chào người dùng trong email với" - setting_oauth_allow_remapping_of_existing_users: "Allow remapping of existing users" + setting_emails_salutation: "Địa chỉ người dùng trong email với" + setting_oauth_allow_remapping_of_existing_users: "Cho phép ánh xạ lại người dùng hiện tại" setting_sendmail_location: "Vị trí của tệp thực thi sendmail (đường dẫn)" - setting_sendmail_arguments: "Arguments for sendmail" + setting_sendmail_arguments: "Đối số cho sendmail" setting_smtp_enable_starttls_auto: "Tự động sử dụng STARTTLS nếu hiện hữu" setting_smtp_ssl: "Sử dụng kết nối SSL" setting_smtp_address: "Máy chủ SMTP" @@ -4306,334 +4304,334 @@ vi: setting_smtp_user_name: "Tên truy cập SMTP" setting_smtp_password: "Mật khẩu SMTP" setting_smtp_domain: "Tên miền SMTP HELO" - setting_activity_days_default: "Số ngày hiển thị trong hoạt động dự án" - setting_app_subtitle: "Phụ đề ứng dụng" + setting_activity_days_default: "Ngày hiển thị trên hoạt động dự án" + setting_app_subtitle: "phụ đề ứng dụng" setting_app_title: "Tiêu đề ứng dụng" - setting_attachment_max_size: "Kích thước tối đa tệp đính kèm" - setting_show_work_package_attachments: "Hiển thị các tệp đính kèm trong tab tệp theo mặc định" + setting_attachment_max_size: "Tệp đính kèm tối đa. kích cỡ" + setting_show_work_package_attachments: "Hiển thị tệp đính kèm trong tab tệp theo mặc định" setting_antivirus_scan_mode: "Chế độ quét" - setting_antivirus_scan_action: "Hành động đối với tệp nhiễm virus" - setting_autofetch_changesets: "Tự động lấy các thay đổi của kho" + setting_antivirus_scan_action: "Hành động tập tin bị nhiễm" + setting_autofetch_changesets: "Tự động tìm nạp thay đổi kho lưu trữ" setting_autologin: "Tự động đăng nhập" - setting_available_languages: "Các ngôn ngữ có sẵn" - setting_bcc_recipients: "Người nhận bản sao ẩn (bcc)" + setting_available_languages: "Ngôn ngữ có sẵn" + setting_bcc_recipients: "Người nhận bản sao mù (bcc)" setting_brute_force_block_after_failed_logins: "Chặn người dùng sau số lần đăng nhập thất bại này" setting_brute_force_block_minutes: "Thời gian người dùng bị chặn" - setting_cache_formatted_text: "Lưu trữ văn bản đã định dạng" + setting_cache_formatted_text: "Văn bản được định dạng bộ đệm" setting_use_wysiwyg_description: "Chọn để cho phép bộ soạn thảo CKEditor đối với tất cả người dùng mặc định. CKEditor đã được hạn chế bởi GFM Markdown." - setting_column_options: "Các cột danh sách công việc mặc định" - setting_commit_fix_keywords: "Từ khóa sửa lỗi" - setting_commit_logs_encoding: "Mã hóa thông điệp cam kết" - setting_commit_logtime_activity_id: "Hoạt động cho thời gian đã ghi" - setting_commit_logtime_enabled: "Kích hoạt ghi thời gian" - setting_commit_ref_keywords: "Từ khóa tham chiếu" + setting_column_options: "Các cột liệt kê gói công việc mặc định" + setting_commit_fix_keywords: "Đang sửa từ khóa" + setting_commit_logs_encoding: "Cam kết mã hóa tin nhắn" + setting_commit_logtime_activity_id: "Hoạt động cho thời gian đã đăng nhập" + setting_commit_logtime_enabled: "Bật ghi thời gian" + setting_commit_ref_keywords: "Từ khóa tham khảo" setting_consent_time: "Thời gian đồng ý" setting_consent_info: "Văn bản thông tin đồng ý" - setting_consent_required: "Cần đồng ý" - setting_consent_decline_mail: "Địa chỉ email liên hệ từ chối đồng ý" - setting_cross_project_work_package_relations: "Cho phép các mối quan hệ gói công việc giữa các dự án" + setting_consent_required: "Cần có sự đồng ý" + setting_consent_decline_mail: "Địa chỉ email liên hệ đồng ý" + setting_cross_project_work_package_relations: "Cho phép quan hệ gói công việc giữa các dự án" setting_first_week_of_year: "Tuần đầu tiên trong năm" - setting_date_format: "Ngày" + setting_date_format: "ngày" setting_default_language: "Ngôn ngữ mặc định" - setting_default_projects_modules: "Các mô-đun kích hoạt mặc định cho dự án mới" - setting_default_projects_public: "Dự án mới là công khai theo mặc định" - setting_disable_password_login: "Disable password authentication" - setting_diff_max_lines_displayed: "Số dòng khác biệt tối đa hiển thị" - setting_omniauth_direct_login_provider: "Direct login SSO provider" - setting_display_subprojects_work_packages: "Hiển thị các gói công việc của các dự án phụ trên các dự án chính theo mặc định" - setting_duration_format: "Định dạng thời gian" + setting_default_projects_modules: "Các mô-đun được bật mặc định cho các dự án mới" + setting_default_projects_public: "Các dự án mới được công khai theo mặc định" + setting_disable_password_login: "Vô hiệu hóa xác thực mật khẩu" + setting_diff_max_lines_displayed: "Số dòng khác biệt tối đa được hiển thị" + setting_omniauth_direct_login_provider: "Nhà cung cấp SSO đăng nhập trực tiếp" + setting_display_subprojects_work_packages: "Hiển thị các gói công việc của dự án con trên các dự án chính theo mặc định" + setting_duration_format: "Định dạng thời lượng" setting_duration_format_hours_only: "Chỉ giờ" setting_duration_format_days_and_hours: "Ngày và giờ" - setting_duration_format_instructions: "Điều này xác định cách hiển thị thời gian công việc, thời gian còn lại và thời gian đã sử dụng." + setting_duration_format_instructions: "Điều này xác định cách hiển thị Công việc, Công việc còn lại và Khoảng thời gian đã sử dụng." setting_emails_footer: "Chân trang email" - setting_emails_header: "Đầu trang email" - setting_email_login: "Sử dụng email làm đăng nhập" - setting_enabled_scm: "SCM được kích hoạt" + setting_emails_header: "Tiêu đề email" + setting_email_login: "Sử dụng email làm thông tin đăng nhập" + setting_enabled_scm: "SCM đã bật" setting_enabled_projects_columns: "Các cột trong danh sách dự án được hiển thị theo mặc định" - setting_feeds_enabled: "Kích hoạt Feeds" + setting_feeds_enabled: "Bật nguồn cấp dữ liệu" setting_ical_enabled: "Kích hoạt đăng ký iCalendar" - setting_feeds_limit: "Giới hạn nội dung của feed" - setting_file_max_size_displayed: "Kích thước tối đa của tệp văn bản hiển thị nội tuyến" + setting_feeds_limit: "Giới hạn nội dung nguồn cấp dữ liệu" + setting_file_max_size_displayed: "Kích thước tối đa của tệp văn bản được hiển thị nội tuyến" setting_host_name: "Tên máy chủ" - setting_collaborative_editing_hocuspocus_url: "Hocuspocus server URL" - setting_collaborative_editing_hocuspocus_secret: "Hocuspocus server secret" + setting_collaborative_editing_hocuspocus_url: "URL máy chủ Hocuspocus" + setting_collaborative_editing_hocuspocus_secret: "Bí mật máy chủ Hocuspocus" setting_hours_per_day: "Giờ mỗi ngày" setting_hours_per_day_explanation: >- - Điều này xác định cái gì được coi là một "ngày" khi hiển thị thời gian theo ngày và giờ (ví dụ, nếu một ngày là 8 giờ, 32 giờ sẽ là 4 ngày). + Điều này xác định thế nào được coi là "ngày" khi hiển thị thời lượng theo ngày và giờ (ví dụ: nếu một ngày là 8 giờ thì 32 giờ sẽ là 4 ngày). setting_invitation_expiration_days: "Email kích hoạt hết hạn sau" - setting_invitation_expiration_days_caption: "Number of days after which the activation email expires." - setting_work_package_done_ratio: "Progress calculation mode" + setting_invitation_expiration_days_caption: "Số ngày sau đó email kích hoạt hết hạn." + setting_work_package_done_ratio: "Chế độ tính toán tiến độ" setting_work_package_done_ratio_field: "Dựa trên công việc" setting_work_package_done_ratio_field_caption_html: >- - % Complete can be freely set to any value. If you optionally enter a value for Work, Remaining work will automatically be derived. + % Hoàn thành có thể được đặt tùy ý thành bất kỳ giá trị nào. Nếu bạn tùy ý nhập giá trị cho Work, Công việc còn lại sẽ tự động được dẫn xuất. setting_work_package_done_ratio_status: "Dựa trên trạng thái" setting_work_package_done_ratio_status_caption_html: >- - Each status has a % Complete value associated with it. Changing status will change % Complete. + Mỗi trạng thái có một giá trị % Hoàn thành được liên kết với nó. Việc thay đổi trạng thái sẽ thay đổi % Hoàn thành. setting_work_package_done_ratio_explanation_html: > - Ở chế độ dựa trên công việc, % Hoàn thành có thể được tự do thiết lập thành bất kỳ giá trị nào. Nếu bạn tùy ý nhập giá trị cho Công việc, Công việc còn lại sẽ tự động được suy ra. Ở chế độ dựa trên trạng thái, mỗi trạng thái có giá trị % Hoàn thành được liên kết với nó. Thay đổi trạng thái sẽ thay đổi % Hoàn thành. + Ở chế độ work-based, % Hoàn thành có thể được đặt tùy ý thành bất kỳ giá trị nào. Nếu bạn tùy ý nhập một giá trị cho Công việc, Công việc còn lại sẽ tự động được bắt nguồn. Trong chế độ dựa trên trạng thái, mỗi trạng thái có một giá trị % Hoàn thành được liên kết với trạng thái đó. Việc thay đổi trạng thái sẽ thay đổi % Hoàn thành. setting_work_package_properties: "Thuộc tính gói công việc" setting_work_package_startdate_is_adddate: "Sử dụng ngày hiện tại làm ngày bắt đầu cho các gói công việc mới" - setting_work_packages_projects_export_limit: "Giới hạn xuất gói công việc / Dự án" + setting_work_packages_projects_export_limit: "Gói công việc / Giới hạn xuất dự án" setting_journal_aggregation_time_minutes: "Hành động của người dùng được tổng hợp trong" - setting_log_requesting_user: "Ghi lại người dùng đăng nhập, tên, và địa chỉ email cho tất cả các yêu cầu" + setting_log_requesting_user: "Ghi nhật ký thông tin đăng nhập, tên và địa chỉ thư của người dùng cho tất cả các yêu cầu" setting_login_required: "Yêu cầu xác thực" - setting_login_required_caption: "When checked, all requests to the application have to be authenticated." - setting_lost_password: "Enable password reset" - setting_lost_password_caption: "When checked, allow users to reset their own passwords." - setting_mail_from: "Địa chỉ email phát hành" + setting_login_required_caption: "Khi được chọn, tất cả các yêu cầu tới ứng dụng phải được xác thực." + setting_lost_password: "Bật đặt lại mật khẩu" + setting_lost_password_caption: "Khi được chọn, hãy cho phép người dùng đặt lại mật khẩu của riêng họ." + setting_mail_from: "Địa chỉ email phát thải" setting_mail_handler_api_key: "Khóa API" - setting_mail_handler_body_delimiters: "Cắt ngắn email sau một trong những dòng này" + setting_mail_handler_body_delimiters: "Cắt bớt email sau một trong những dòng này" setting_mail_handler_body_delimiter_regex: "Xén các email phù hợp với biểu thức này" setting_mail_handler_ignore_filenames: "Tập tin đính kèm mail bị bỏ qua" - setting_new_project_user_role_id: "Vai trò được cấp cho người dùng không phải quản trị viên khi tạo một dự án" - setting_new_project_send_confirmation_email: "Send notification to author when creating a new project" - setting_new_project_notification_text: "Notification text" - setting_password_active_rules: "Các lớp ký tự hoạt động" - setting_password_count_former_banned: "Số lượng mật khẩu gần đây bị cấm tái sử dụng" - setting_password_days_valid: "Số ngày, sau đó yêu cầu thay đổi mật khẩu" + setting_new_project_user_role_id: "Vai trò được cấp cho người dùng không phải quản trị viên tạo dự án" + setting_new_project_send_confirmation_email: "Gửi thông báo cho tác giả khi tạo dự án mới" + setting_new_project_notification_text: "Văn bản thông báo" + setting_password_active_rules: "Lớp nhân vật hoạt động" + setting_password_count_former_banned: "Số lượng mật khẩu được sử dụng gần đây nhất bị cấm sử dụng lại" + setting_password_days_valid: "Số ngày thực hiện thay đổi mật khẩu sau đó" setting_password_min_length: "Chiều dài tối thiểu" - setting_password_min_adhered_rules: "Số lượng lớp yêu cầu tối thiểu" - setting_per_page_options: "Tùy chọn số đối tượng trên mỗi trang" - setting_percent_complete_on_status_closed: "% Complete when status is closed" - setting_percent_complete_on_status_closed_no_change: "No change" + setting_password_min_adhered_rules: "Số lượng lớp học yêu cầu tối thiểu" + setting_per_page_options: "Tùy chọn đối tượng trên mỗi trang" + setting_percent_complete_on_status_closed: "% Hoàn thành khi trạng thái đóng" + setting_percent_complete_on_status_closed_no_change: "Không thay đổi" setting_percent_complete_on_status_closed_no_change_caption_html: >- - The value of % Complete will not change even when a work package is closed. - setting_percent_complete_on_status_closed_set_100p: "Automatically set to 100%" + Giá trị của % Complete sẽ không thay đổi ngay cả khi đóng gói công việc. + setting_percent_complete_on_status_closed_set_100p: "Tự động đặt thành 100%" setting_percent_complete_on_status_closed_set_100p_caption: >- - A closed work package is considered complete. - setting_plain_text_mail: "Email văn bản thuần túy (không có HTML)" + Một gói công việc khép kín được coi là hoàn thành. + setting_plain_text_mail: "Thư văn bản thuần túy (không có HTML)" setting_protocol: "Giao thức" - setting_project_gantt_query: "Xem Gantt của danh mục dự án" - setting_project_gantt_query_text: "Bạn có thể chỉnh sửa truy vấn được sử dụng để hiển thị biểu đồ Gantt từ trang tổng quan dự án." + setting_project_gantt_query: "Danh mục dự án Chế độ xem Gantt" + setting_project_gantt_query_text: "Bạn có thể sửa đổi truy vấn được sử dụng để hiển thị biểu đồ Gantt từ trang tổng quan dự án." setting_security_badge_displayed: "Hiển thị huy hiệu bảo mật" setting_registration_footer: "Chân trang đăng ký" - setting_registration_footer_caption: "This text is displayed in the footer of the registration page. Use the HTML editor to format the text for each selected language." - setting_repositories_automatic_managed_vendor: "Loại nhà cung cấp kho tự động" - setting_repositories_encodings: "Mã hóa kho" - setting_repository_storage_cache_minutes: "Bộ nhớ cache kích thước đĩa kho" - setting_repository_checkout_display: "Hiển thị hướng dẫn kiểm tra" - setting_repository_checkout_base_url: "URL cơ sở kiểm tra" - setting_repository_checkout_text: "Văn bản hướng dẫn kiểm tra" - setting_repository_log_display_limit: "Số lượng sửa đổi tối đa hiển thị trên nhật ký tệp" - setting_repository_truncate_at: "Số lượng tệp tối đa hiển thị trong trình duyệt kho" + setting_registration_footer_caption: "Văn bản này được hiển thị ở chân trang của trang đăng ký. Sử dụng trình soạn thảo HTML để định dạng văn bản cho từng ngôn ngữ đã chọn." + setting_repositories_automatic_managed_vendor: "Loại nhà cung cấp kho lưu trữ tự động" + setting_repositories_encodings: "Mã hóa kho lưu trữ" + setting_repository_storage_cache_minutes: "Bộ đệm kích thước đĩa lưu trữ" + setting_repository_checkout_display: "Hiển thị hướng dẫn thanh toán" + setting_repository_checkout_base_url: "URL cơ sở thanh toán" + setting_repository_checkout_text: "Văn bản hướng dẫn thanh toán" + setting_repository_log_display_limit: "Số lượng bản sửa đổi tối đa được hiển thị trên nhật ký tệp" + setting_repository_truncate_at: "Số lượng tệp tối đa được hiển thị trong trình duyệt kho lưu trữ" setting_rest_api_enabled: "Kích hoạt dịch vụ web REST" - setting_self_registration: "Đăng ký tự động" + setting_self_registration: "Tự đăng ký" setting_self_registration_caption: > - Choose the self-registration mechanism for users. Be careful with the setting you choose, as some options allow users to activate their own accounts to this instance. + Lựa chọn cơ chế tự đăng ký cho người dùng. Hãy cẩn thận với cài đặt bạn chọn vì một số tùy chọn cho phép người dùng kích hoạt tài khoản của chính họ trong trường hợp này. setting_self_registration_warning: > - The user will be able to activate their own accounts. Please note that this will give them access to all public projects and their content. Please make sure that no sensitive or private data is exposed in public projects. - setting_self_registration_disabled: "VÔ HIỆU" + Người dùng sẽ có thể kích hoạt tài khoản của riêng họ. Xin lưu ý rằng điều này sẽ cấp cho họ quyền truy cập vào tất cả các dự án công cộng và nội dung của chúng. Hãy đảm bảo rằng không có dữ liệu nhạy cảm hoặc riêng tư nào bị lộ trong các dự án công cộng. + setting_self_registration_disabled: "bị vô hiệu hóa" setting_self_registration_disabled_caption: > - No accounts can be registered on their own. Only administrators and users with the global permission to create new users are able to create new accounts. - setting_self_registration_activation_by_email: "Account activation by email" + Không có tài khoản nào có thể được đăng ký riêng. Chỉ quản trị viên và người dùng có quyền toàn cầu để tạo người dùng mới mới có thể tạo tài khoản mới. + setting_self_registration_activation_by_email: "Kích hoạt tài khoản qua email" setting_self_registration_activation_by_email_caption: > - Users can register on their own and activate their account after confirming their email address. Administrators have no moderation control over the activation process. - setting_self_registration_automatic_activation: "Automatic account activation" + Người dùng có thể tự đăng ký và kích hoạt tài khoản sau khi xác nhận địa chỉ email của họ. Quản trị viên không có quyền kiểm duyệt quá trình kích hoạt. + setting_self_registration_automatic_activation: "Kích hoạt tài khoản tự động" setting_self_registration_automatic_activation_caption: > - Users can register on their own. Their accounts are immediately active without further action. Administrators have no moderation control over the activation process. - setting_self_registration_manual_activation: "Manual account activation" + Người dùng có thể tự đăng ký. Tài khoản của họ ngay lập tức hoạt động mà không cần thực hiện thêm hành động nào. Quản trị viên không có quyền kiểm duyệt quá trình kích hoạt. + setting_self_registration_manual_activation: "Kích hoạt tài khoản thủ công" setting_self_registration_manual_activation_caption: > - Users can register on their own. Their accounts are in a pending state until an administrator or user with the global permission to create or manage users activates them. - setting_session_ttl: "Session expiration time after inactivity" + Người dùng có thể tự đăng ký. Tài khoản của họ ở trạng thái chờ xử lý cho đến khi quản trị viên hoặc người dùng có quyền toàn cầu tạo hoặc quản lý người dùng kích hoạt chúng. + setting_session_ttl: "Thời gian hết hạn phiên sau khi không hoạt động" setting_session_ttl_hint: "Giá trị dưới 5 hoạt động như bị vô hiệu hóa" setting_session_ttl_enabled: "Phiên hết hạn" setting_start_of_week: "Tuần Bắt đầu" - setting_sys_api_enabled: "Kích hoạt dịch vụ quản lý kho" - setting_sys_api_description: "Dịch vụ quản lý kho cung cấp tích hợp và xác thực người dùng cho việc truy cập kho." - setting_time_format: "Thời gian" - setting_total_percent_complete_mode: "Calculation of % Complete hierarchy totals" - setting_total_percent_complete_mode_work_weighted_average: "Weighted by work" + setting_sys_api_enabled: "Kích hoạt dịch vụ web quản lý kho lưu trữ" + setting_sys_api_description: "Dịch vụ web quản lý kho lưu trữ cung cấp sự tích hợp và ủy quyền cho người dùng để truy cập vào kho lưu trữ." + setting_time_format: "thời gian" + setting_total_percent_complete_mode: "Tính % tổng số thứ bậc hoàn thành" + setting_total_percent_complete_mode_work_weighted_average: "Bị đè nặng bởi công việc" setting_total_percent_complete_mode_work_weighted_average_caption_html: >- - The total % Complete will be weighted against the Work of each work package in the hierarchy. Work packages without Work will be ignored. - setting_total_percent_complete_mode_simple_average: "Simple average" + total % Complete sẽ được tính trọng số theo Work của từng gói công việc trong hệ thống phân cấp. Các gói công việc không có Work sẽ bị bỏ qua. + setting_total_percent_complete_mode_simple_average: "Trung bình đơn giản" setting_total_percent_complete_mode_simple_average_caption_html: >- - Work is ignored and the total % Complete will be a simple average of % Complete values of work packages in the hierarchy. - setting_accessibility_mode_for_anonymous: "Kích hoạt chế độ truy cập cho người dùng ẩn danh" + Work bị bỏ qua và total % Complete sẽ là giá trị trung bình đơn giản của các giá trị % Complete của các gói công việc trong hệ thống phân cấp. + setting_accessibility_mode_for_anonymous: "Bật chế độ trợ năng cho người dùng ẩn danh" setting_user_format: "Định dạng tên người dùng" setting_user_default_timezone: "Time zone mặc định" - setting_users_deletable_by_admins: "Tài khoản người dùng có thể bị xóa bởi quản trị viên" - setting_users_deletable_by_self: "Người dùng có thể xóa tài khoản của họ" - setting_welcome_text: "Văn bản chào mừng" - setting_welcome_title: "Tiêu đề chào mừng" + setting_users_deletable_by_admins: "Tài khoản người dùng có thể bị quản trị viên xóa" + setting_users_deletable_by_self: "Người dùng được phép xóa tài khoản của họ" + setting_welcome_text: "Văn bản chặn chào mừng" + setting_welcome_title: "Tiêu đề khối chào mừng" setting_welcome_on_homescreen: "Hiển thị khối chào mừng trên màn hình chính" - setting_work_package_list_default_highlighting_mode: "Chế độ tô sáng mặc định" - setting_work_package_list_default_highlighted_attributes: "Các thuộc tính được tô sáng mặc định trong dòng" + setting_work_package_list_default_highlighting_mode: "Chế độ đánh dấu mặc định" + setting_work_package_list_default_highlighted_attributes: "Thuộc tính được đánh dấu nội tuyến mặc định" setting_working_days: "Ngày làm việc" settings: errors: - not_writable: "The setting is not writable and can only be changed by a sysadmin." - failed_to_update: "Setting '%{name}' could not be updated: %{message}" + not_writable: "Cài đặt này không thể ghi và chỉ có thể được thay đổi bởi quản trị viên hệ thống." + failed_to_update: "Không thể cập nhật cài đặt '%{name}': %{message}" authentication: - login: "Đăng nhập" - registration: "Registration" - sso: "Single Sign-On (SSO)" + login: "đăng nhập" + registration: "đăng ký" + sso: "Đăng nhập một lần (SSO)" omniauth_direct_login_hint_html: > - If this option is active, login requests will redirect to the configured omniauth provider. The login dropdown and sign-in page will be disabled.
    Note: Unless you also disable password logins, with this option enabled, users can still log in internally by visiting the %{internal_path} login page. + Nếu tùy chọn này đang hoạt động, yêu cầu đăng nhập sẽ chuyển hướng đến nhà cung cấp omniauth đã định cấu hình. Trang đăng nhập thả xuống và trang đăng nhập sẽ bị vô hiệu hóa.
    Lưu ý: Trừ khi bạn cũng tắt tính năng đăng nhập bằng mật khẩu, khi bật tùy chọn này, người dùng vẫn có thể đăng nhập nội bộ bằng cách truy cập trang đăng nhập %{internal_path}. remapping_existing_users_hint: > - If enabled, allows any configured identity provider to login existing users based on their username, even if the user never signed in through that provider before. This can be useful when migrating the OpenProject instance to a new SSO provider, but is not recommended when using a provider that is not trusted by all users of your instance. + Nếu được bật, sẽ cho phép mọi nhà cung cấp danh tính được định cấu hình đăng nhập người dùng hiện tại dựa trên tên người dùng của họ, ngay cả khi người dùng chưa từng đăng nhập thông qua nhà cung cấp đó trước đây. Điều này có thể hữu ích khi di chuyển phiên bản OpenProject sang nhà cung cấp SSO mới, nhưng không được khuyến nghị khi sử dụng nhà cung cấp không được tất cả người dùng phiên bản của bạn tin cậy. attachments: whitelist_text_html: > - Xác định danh sách các phần mở rộng tệp hợp lệ và/hoặc loại mime cho các tệp tải lên.
    Nhập các phần mở rộng tệp (ví dụ: %{ext_example}) hoặc loại mime (ví dụ: %{mime_example}).
    Để trống để cho phép tải lên bất kỳ loại tệp nào. Nhiều giá trị được phép (mỗi giá trị trên một dòng). + Xác định danh sách các phần mở rộng tệp hợp lệ và/hoặc loại mime cho các tệp được tải lên.
    Nhập phần mở rộng tệp (ví dụ: %{ext_example}) hoặc loại mime (ví dụ: %{mime_example}).
    Để trống để cho phép tải lên mọi loại tệp. Cho phép nhiều giá trị (mỗi giá trị một dòng). show_work_package_attachments: > - Vô hiệu hóa tùy chọn này sẽ ẩn danh sách đính kèm trên tab tệp gói công việc cho các dự án mới. Các tệp đính kèm trong mô tả của một gói công việc vẫn sẽ được tải lên kho lưu trữ đính kèm nội bộ. + Việc tắt tùy chọn này sẽ ẩn danh sách tệp đính kèm trên tab tệp gói công việc cho các dự án mới. Các tệp đính kèm trong phần mô tả của gói công việc sẽ vẫn được tải lên bộ lưu trữ tệp đính kèm nội bộ. antivirus: title: "Quét virus" - clamav_ping_failed: "Không thể kết nối với daemon ClamAV. Vui lòng kiểm tra cấu hình và thử lại." + clamav_ping_failed: "Không thể kết nối trình nền ClamAV. Hãy kiểm tra kỹ cấu hình và thử lại." remaining_quarantined_files_html: > Quét virus đã bị vô hiệu hóa. Có %{file_count} tệp vẫn còn trong cách ly. Để xem các tệp đã cách ly, vui lòng truy cập liên kết này: %{link} remaining_scan_complete_html: > - Các tệp còn lại đã được quét. Có %{file_count} tệp trong cách ly. Bạn sẽ được chuyển hướng đến trang cách ly. Sử dụng trang này để xóa hoặc bỏ qua các tệp đã cách ly. + Các tập tin còn lại đã được quét. Có %{file_count} đang bị cách ly. Bạn đang được chuyển hướng đến trang cách ly. Sử dụng trang này để xóa hoặc ghi đè các tập tin đã cách ly. remaining_rescanned_files: > - Quét virus đã được kích hoạt thành công. Có %{file_count} tệp đã được tải lên trước đó và vẫn cần được quét. Quá trình này đã được lên lịch chạy nền. Các tệp sẽ vẫn có thể truy cập trong quá trình quét. + Quét virus đã được kích hoạt thành công. Có %{file_count} đã được tải lên trước đó và vẫn cần được quét. Quá trình này đã được lên lịch ở chế độ nền. Các tập tin sẽ vẫn có thể truy cập được trong quá trình quét. actions: - delete: "Xóa tệp" - quarantine: "Cách ly tệp" + delete: "Xóa tập tin" + quarantine: "Cách ly tập tin" instructions_html: > Chọn hành động để thực hiện đối với các tệp có virus đã được phát hiện:
    • %{quarantine_option}: Cách ly tệp, ngăn chặn người dùng truy cập vào nó. Các quản trị viên có thể xem và xóa các tệp đã cách ly trong quản trị.
    • %{delete_option}: Xóa tệp ngay lập tức.
    modes: - clamav_socket_html: 'Nhập ổ cắm vào daemon clamd, ví dụ: %{example}' - clamav_host_html: 'Nhập tên máy chủ và cổng vào daemon clamd được phân tách bằng dấu hai chấm. Ví dụ: %{example}' + clamav_socket_html: 'Nhập ổ cắm vào daemon ngao, ví dụ: %{example}' + clamav_host_html: 'Nhập tên máy chủ và cổng vào trình nền Clamd được phân tách bằng dấu hai chấm. ví dụ: %{example}' description_html: > Chọn chế độ mà tích hợp trình quét antivirus nên hoạt động.
    • %{disabled_option}: Các tệp tải lên sẽ không được quét virus.
    • %{socket_option}: Bạn đã thiết lập ClamAV trên cùng một máy chủ với OpenProject và daemon quét clamd đang chạy nền
    • %{host_option}: Bạn đang truyền tệp đến một máy chủ quét virus bên ngoài.
    - brute_force_prevention: "Ngăn chặn người dùng tự động" + brute_force_prevention: "Tự động chặn người dùng" date_format: first_date_of_week_and_year_set: > - Nếu một trong các tùy chọn "%{day_of_week_setting_name}" hoặc "%{first_week_setting_name}" được đặt, cái còn lại cũng phải được đặt để tránh sự không nhất quán trên giao diện người dùng. + Nếu một trong hai tùy chọn "%{day_of_week_setting_name}" hoặc "%{first_week_setting_name}" được đặt thì tùy chọn còn lại cũng phải được đặt để tránh sự mâu thuẫn ở giao diện người dùng. first_week_of_year_text_html: > Chọn ngày của tháng Một thuộc tuần đầu tiên của năm. Giá trị này cùng với ngày đầu tiên của tuần xác định tổng số tuần trong năm. Để biết thêm thông tin, vui lòng xem tài liệu của chúng tôi về chủ đề này. experimental: - save_confirmation: Cẩn thận! Nguy cơ mất dữ liệu! Chỉ kích hoạt các tính năng thử nghiệm nếu bạn không ngại làm hỏng cài đặt OpenProject của mình và mất tất cả dữ liệu của nó. - warning_toast: Cờ tính năng là các cài đặt kích hoạt các tính năng vẫn đang được phát triển. Chúng chỉ nên được sử dụng cho mục đích kiểm tra. Chúng không bao giờ nên được kích hoạt trên các cài đặt OpenProject chứa dữ liệu quan trọng. Những tính năng này rất có thể sẽ làm hỏng dữ liệu của bạn. Sử dụng chúng theo rủi ro của bạn. + save_confirmation: Thận trọng! Nguy cơ mất dữ liệu! Chỉ kích hoạt các tính năng thử nghiệm nếu bạn không ngại phá vỡ cài đặt OpenProject và mất tất cả dữ liệu của nó. + warning_toast: Cờ tính năng là cài đặt kích hoạt các tính năng vẫn đang được phát triển. Chúng chỉ được sử dụng cho mục đích thử nghiệm. Chúng sẽ không bao giờ được kích hoạt trên các bản cài đặt OpenProject chứa dữ liệu quan trọng. Những tính năng này rất có thể sẽ làm hỏng dữ liệu của bạn. Sử dụng chúng có nguy cơ của riêng bạn. feature_flags: Cờ tính năng - general: "Tổng quan" + general: "chung" highlighting: mode_long: - inline: "Tô sáng thuộc tính (các) trực tiếp" - none: "Không có đánh dấu" + inline: "Đánh dấu (các) thuộc tính nội tuyến" + none: "Không làm nổi bật" status: "Toàn bộ hàng theo Trạng thái" - type: "Toàn bộ hàng theo Loại" - priority: "Toàn bộ hàng theo Mức độ ưu tiên" + type: "Toàn bộ hàng theo loại" + priority: "Toàn bộ hàng theo mức độ ưu tiên" icalendar: enable_subscriptions_text_html: Cho phép người dùng với quyền cần thiết đăng ký các lịch OpenProject và truy cập thông tin gói công việc qua một ứng dụng lịch bên ngoài. Lưu ý: Vui lòng đọc về đăng ký iCalendar để hiểu các rủi ro bảo mật tiềm ẩn trước khi kích hoạt tính năng này. language_name_being_default: "%{language_name} (mặc định)" notifications: - events_explanation: "Quy định cho sự kiện nào một email được gửi đi. Các gói công việc bị loại trừ khỏi danh sách này vì thông báo cho chúng có thể được cấu hình riêng cho mỗi người dùng." - delay_minutes_explanation: "Gửi email có thể bị trì hoãn để cho phép người dùng với thông báo trong ứng dụng được xác nhận thông báo trong ứng dụng trước khi email được gửi đi. Người dùng đã đọc một thông báo trong ứng dụng sẽ không nhận được email cho thông báo đã đọc." + events_explanation: "Quản lý sự kiện mà email được gửi đi. Các gói công việc được loại trừ khỏi danh sách này vì thông báo cho chúng có thể được cấu hình cụ thể cho từng người dùng." + delay_minutes_explanation: "Việc gửi email có thể bị trì hoãn để cho phép người dùng đã định cấu hình thông báo trong ứng dụng xác nhận thông báo trong ứng dụng trước khi thư được gửi đi. Người dùng đọc thông báo trong ứng dụng sẽ không nhận được email cho thông báo đã đọc." other: "Khác" - passwords: "Mật khẩu" + passwords: "mật khẩu" project_attributes: heading: "Các thuộc tính dự án" - label_for_all_projects: "All projects" + label_for_all_projects: "Tất cả dự án" label_new_attribute: "Thuộc tính dự án" - label_new_section: "Phần" + label_new_section: "phần" label_edit_section: "Chỉnh sửa tiêu đề" - label_section_actions: "Hành động phần" - heading_description: "Các thuộc tính dự án này xuất hiện trên trang tổng quan của mỗi dự án. Bạn có thể thêm thuộc tính mới, nhóm chúng thành các phần và sắp xếp chúng theo ý thích. Các thuộc tính này có thể được bật hoặc tắt nhưng không thể sắp xếp lại ở cấp dự án." + label_section_actions: "Hành động của phần" + heading_description: "Các thuộc tính dự án này xuất hiện trong trang tổng quan của từng dự án. Bạn có thể thêm các thuộc tính mới, nhóm chúng thành các phần và sắp xếp lại chúng theo ý muốn. Các thuộc tính này có thể được bật hoặc tắt nhưng không được sắp xếp lại ở cấp dự án." label_project_custom_field_actions: "Hành động thuộc tính dự án" label_no_project_custom_fields: "Không có thuộc tính dự án nào được xác định trong phần này" edit: - description: "Những thay đổi đối với thuộc tính dự án này sẽ được phản ánh trong tất cả các dự án mà nó được bật. Các thuộc tính bắt buộc không thể bị tắt ở mức dự án." + description: "Những thay đổi đối với thuộc tính dự án này sẽ được phản ánh trong tất cả các dự án được bật. Các thuộc tính bắt buộc không thể bị vô hiệu hóa trên cơ sở từng dự án." new: heading: "Thuộc tính mới" - description: "Những thay đổi đối với thuộc tính dự án này sẽ được phản ánh trong tất cả các dự án mà nó được bật. Các thuộc tính bắt buộc không thể bị tắt ở mức dự án." + description: "Những thay đổi đối với thuộc tính dự án này sẽ được phản ánh trong tất cả các dự án được bật. Các thuộc tính bắt buộc không thể bị vô hiệu hóa trên cơ sở từng dự án." sections: display_representation: overview: - label: "Project attribute shown in:" + label: "Thuộc tính dự án được hiển thị trong:" main_area: - label: "Main area" - description: "Add all the project attributes as individual widgets in the main section of the project overview." + label: "Khu vực chính" + description: "Thêm tất cả thuộc tính dự án dưới dạng các tiện ích riêng lẻ trong phần chính của tổng quan dự án." side_panel: - label: "Side panel" - description: "Add all the project attributes in a section inside the right side panel in the project overview." + label: "Bảng điều khiển bên" + description: "Thêm tất cả thuộc tính dự án vào một phần bên trong bảng điều khiển bên phải trong phần tổng quan về dự án." project_initiation_request: header_description: > - OpenProject can generate a step-by-step wizard to help project managers fill out a project initiation request. You can choose which project attributes should be included and create a PDF artifact as a result. + OpenProject có thể tạo trình hướng dẫn từng bước để giúp người quản lý dự án điền vào yêu cầu khởi tạo dự án. Bạn có thể chọn những thuộc tính dự án nào sẽ được đưa vào và kết quả là tạo một tạo phẩm PDF. status: - submitted: "%{wizard_name} has been submitted" - submitted_description: "Click the button below to go to the work package for the submission process." - submitted_button: "Open submission request" - not_completed: "%{wizard_name} not yet completed" - not_completed_description: "Provide the necessary information by filling the attributes and get the project started." + submitted: "%{wizard_name} đã được gửi" + submitted_description: "Nhấp vào nút bên dưới để chuyển đến gói công việc cho quá trình gửi bài." + submitted_button: "Mở yêu cầu gửi" + not_completed: "%{wizard_name} chưa hoàn thành" + not_completed_description: "Cung cấp thông tin cần thiết bằng cách điền vào các thuộc tính và bắt đầu dự án." wizard_status_button: - project_initiation_request: "Open project initiation request" - project_creation_wizard: "Open project creation wizard" - project_mandate: "Open project mandate" + project_initiation_request: "Mở yêu cầu bắt đầu dự án" + project_creation_wizard: "Mở trình hướng dẫn tạo dự án" + project_mandate: "Nhiệm vụ mở dự án" blankslate: - title: "Initiation request not enabled" - description: "OpenProject can generate a step-by-step wizard to help project managers fill out a project initiation request. You can choose which project attributes should be included and what to do with the output document. Enable it here to start configuring the wizard." + title: "Yêu cầu bắt đầu không được kích hoạt" + description: "OpenProject có thể tạo trình hướng dẫn từng bước để giúp người quản lý dự án điền vào yêu cầu khởi tạo dự án. Bạn có thể chọn những thuộc tính dự án nào sẽ được đưa vào và những việc cần làm với tài liệu đầu ra. Kích hoạt nó ở đây để bắt đầu cấu hình trình hướng dẫn." disable_dialog: - title: "Disable project initiation request" - heading: "Disable this project initiation request?" - confirmation_message: "The initiation request wizard will no longer be available to new projects based on this template. Project managers and project owners will need to manually configure and fill out the relevant information in the Project overview." - checkbox_message: "I understand that this action is not reversible" + title: "Tắt yêu cầu khởi tạo dự án" + heading: "Tắt yêu cầu bắt đầu dự án này?" + confirmation_message: "Trình hướng dẫn yêu cầu khởi tạo sẽ không còn khả dụng cho các dự án mới dựa trên mẫu này nữa. Người quản lý dự án và chủ sở hữu dự án sẽ cần định cấu hình và điền thông tin liên quan theo cách thủ công trong phần Tổng quan về dự án." + checkbox_message: "Tôi hiểu rằng hành động này không thể đảo ngược" name: - artifact_name: "Artifact name" - artifact_name_caption: "Choose the name for this artifact that your project management framework recommends." + artifact_name: "Tên hiện vật" + artifact_name_caption: "Chọn tên cho tạo phẩm này mà khung quản lý dự án của bạn đề xuất." options: - project_initiation_request: "Project initiation request" - project_creation_wizard: "Project creation wizard" - project_mandate: "Project mandate" + project_initiation_request: "Yêu cầu khởi tạo dự án" + project_creation_wizard: "Trình hướng dẫn tạo dự án" + project_mandate: "Nhiệm vụ dự án" submission: description_template: > - **This work package was automatically created upon completion of the %{wizard_name} workflow.** A PDF artifact containing all submitted information has been generated and attached to this work package for reference and audit purposes. If you need to update or re-run the initiation steps, you can reopen the wizard at any time by using the link below: - description: "When a user submits a project initiation request, a new work package will be created with the request artifact attached as a PDF file. The settings below define the type, status and assignee for this new work package." - work_package_type: "Work package type" - work_package_type_caption: "The work package type that should be used to store the completed artifact." - status_when_submitted: "Status when submitted" - status_when_submitted_caption: "The status the generated work package will transition to once the request is submitted." - send_confirmation_email: "Send confirmation email to the user who submitted the project initiation request" - assignee: "Assignee when submitted" - assignee_caption_html: "The user or group assigned to this project attribute will also become the assignee of the new work package. This list includes active project attributes of type User only." - confirmation_email_text: "Confirmation email text" - confirmation_email_default: "Hello,\n\nYou submitted a project initiation request for **%{project_name}**. It is now awaiting review.\nClick the link below to access the work package with your request." - work_package_comment: "Work package comment" - work_package_comment_caption: "The assignee selected above will automatically be @mentioned in the comment." - work_package_comment_default: "A project initiation request for **%{project_name}** was submitted and is awaiting review." + **Gói công việc này được tạo tự động sau khi hoàn thành quy trình làm việc " %{wizard_name} ".** Một tệp PDF chứa tất cả thông tin đã nộp đã được tạo và đính kèm vào gói công việc này để tham khảo và kiểm toán. Nếu bạn cần cập nhật hoặc chạy lại các bước khởi tạo, bạn có thể mở lại trình hướng dẫn bất cứ lúc nào bằng cách sử dụng liên kết bên dưới: + description: "Khi người dùng gửi yêu cầu bắt đầu dự án, một gói công việc mới sẽ được tạo với tạo phẩm yêu cầu được đính kèm dưới dạng tệp PDF. Các cài đặt bên dưới xác định loại, trạng thái và người được giao cho gói công việc mới này." + work_package_type: "Loại gói công việc" + work_package_type_caption: "Loại gói công việc nên được sử dụng để lưu trữ tạo phẩm đã hoàn thành." + status_when_submitted: "Trạng thái khi gửi" + status_when_submitted_caption: "Trạng thái của gói công việc được tạo sẽ chuyển sang trạng thái sau khi yêu cầu được gửi." + send_confirmation_email: "Gửi email xác nhận cho người dùng đã gửi yêu cầu bắt đầu dự án" + assignee: "Người được chuyển nhượng khi nộp" + assignee_caption_html: "Người dùng hoặc nhóm được gán cho thuộc tính dự án này cũng sẽ trở thành người được chuyển nhượng gói công việc mới. Danh sách này chỉ bao gồm các thuộc tính dự án đang hoạt động thuộc loại User." + confirmation_email_text: "Văn bản email xác nhận" + confirmation_email_default: "Xin chào,\n\nBạn đã gửi yêu cầu bắt đầu dự án cho **%{project_name}**. Bây giờ nó đang chờ xem xét.\nNhấp vào liên kết bên dưới để truy cập gói công việc với yêu cầu của bạn." + work_package_comment: "Nhận xét gói công việc" + work_package_comment_caption: "Người được giao được chọn ở trên sẽ tự động được @đề cập trong nhận xét." + work_package_comment_default: "Yêu cầu bắt đầu dự án cho **%{project_name}** đã được gửi và đang chờ xem xét." project_phase_definitions: - heading: "Project life cycle" - heading_description: "Project life cycle defines the project phases that can be used for your project planning and will appear in the overview page of each project. These attributes can be enabled or disabled but not re-ordered at a project level." + heading: "Vòng đời dự án" + heading_description: "Vòng đời dự án xác định các giai đoạn dự án có thể được sử dụng để lập kế hoạch dự án của bạn và sẽ xuất hiện trên trang tổng quan của từng dự án. Các thuộc tính này có thể được bật hoặc tắt nhưng không được sắp xếp lại ở cấp dự án." label_add: "Thêm" - label_add_description: "Add project phase definition" + label_add_description: "Thêm định nghĩa giai đoạn dự án" filter: - label: "Search project phase" - section_header: "Phases" - non_defined: "No phases are currently defined." - phase_gates: "Phase gates" + label: "Tìm kiếm giai đoạn dự án" + section_header: "giai đoạn" + non_defined: "Hiện tại không có giai đoạn nào được xác định." + phase_gates: "Cổng pha" new: - description: "Changes to this project phase will be reflected in all projects where it is enabled." - heading: "New phase" - both_gate: "Start and finish gate" - no_gate: "No gate" - start_gate: "Start gate" - start_gate_caption: "Add a gate with the start date of the phase" - finish_gate: "Finish gate" - finish_gate_caption: "Add a gate with the end date of the phase" + description: "Những thay đổi đối với giai đoạn dự án này sẽ được phản ánh trong tất cả các dự án được kích hoạt." + heading: "Giai đoạn mới" + both_gate: "Cổng bắt đầu và kết thúc" + no_gate: "Không có cổng" + start_gate: "Cổng bắt đầu" + start_gate_caption: "Thêm một cổng có ngày bắt đầu của giai đoạn" + finish_gate: "cổng kết thúc" + finish_gate_caption: "Thêm một cổng có ngày kết thúc của giai đoạn" projects: - missing_dependencies: "Mô-đun dự án %{module} đã được kiểm tra và phụ thuộc vào %{dependencies}. Bạn cần kiểm tra các phụ thuộc này cũng vậy." - section_new_projects: "Cài đặt cho các dự án mới" + missing_dependencies: "Mô-đun dự án %{module} đã được kiểm tra, điều này phụ thuộc vào %{dependencies}. Bạn cũng cần kiểm tra những phụ thuộc này." + section_new_projects: "Cài đặt cho dự án mới" section_project_overview: "Cài đặt cho danh sách dự án" - session: "Phiên làm việc" + session: "Phiên" user: default_preferences: "Cấu hình mặc định" display_format: "Định dạng hiển thị" deletion: "Xóa" working_days: - section_work_week: "Tuần làm việc" - section_holidays_and_closures: "Ngày nghỉ và đóng cửa" + section_work_week: "tuần làm việc" + section_holidays_and_closures: "Ngày lễ và đóng cửa" work_packages: - not_allowed_text: "You do not have the necessary permissions to view this page." + not_allowed_text: "Bạn không có quyền cần thiết để xem trang này." activities: - enable_internal_comments: "Enable internal comments" - helper_text: "Internal comments allow an internal team to communicate amongst themselves privately. These are only visible to selected roles that have the necessary permissions and will not be visible publicly. %{link}" + enable_internal_comments: "Bật nhận xét nội bộ" + helper_text: "Nhận xét nội bộ cho phép một nhóm nội bộ liên lạc riêng tư với nhau. Những quyền này chỉ hiển thị với những vai trò được chọn có các quyền cần thiết và sẽ không hiển thị công khai. %{link}" text_formatting: markdown: "Gạch dưới" plain: "Văn bản thuần" - status_active: "đang hoạt động" + status_active: "Đang hoạt động" status_archived: "đã lưu trữ" status_blocked: "bị chặn" - status_invited: được mời - status_locked: đã khóa + status_invited: Đã mời + status_locked: bị khóa status_registered: đã đăng ký status_deleted: đã xóa #Used in array.to_sentence. @@ -4641,145 +4639,145 @@ vi: array: sentence_connector: "và" skip_last_comma: "sai" - text_accessibility_hint: "Chế độ truy cập được thiết kế cho người dùng khiếm thị, khuyết tật vận động hoặc thị lực kém. Đối với những người có thị lực kém, các yếu tố được tập trung sẽ được làm nổi bật đặc biệt. Vui lòng lưu ý, mô-đun Backlogs không có sẵn trong chế độ này." - text_access_token_hint: "Mã truy cập cho phép bạn cấp quyền cho các ứng dụng bên ngoài truy cập tài nguyên trong OpenProject." - text_analyze: "Phân tích thêm: %{subject}" + text_accessibility_hint: "Chế độ trợ năng được thiết kế dành cho người dùng bị mù, khuyết tật vận động hoặc có thị lực kém. Đối với các yếu tố tập trung sau này được đánh dấu đặc biệt. Xin lưu ý rằng mô-đun Backlogs không khả dụng ở chế độ này." + text_access_token_hint: "Mã thông báo truy cập cho phép bạn cấp cho các ứng dụng bên ngoài quyền truy cập vào tài nguyên trong OpenProject." + text_analyze: "Phân tích sâu hơn: %{subject}" text_are_you_sure: "Bạn có chắc không?" - open_link_in_a_new_tab: "Open link in a new tab" + open_link_in_a_new_tab: "Mở liên kết trong tab mới" text_are_you_sure_continue: "Bạn có chắc chắn muốn tiếp tục không?" text_are_you_sure_with_children: "Xóa gói công việc và tất cả các gói công việc con?" - text_are_you_sure_with_project_custom_fields: "Xóa thuộc tính này cũng sẽ xóa các giá trị của nó trong tất cả các dự án. Bạn có chắc chắn muốn làm điều này không?" - text_are_you_sure_with_project_life_cycle_step: "Deleting this phase will also delete its usages in all projects. Are you sure you want to do this?" - text_assign_to_project: "Gán cho dự án" + text_are_you_sure_with_project_custom_fields: "Xóa thuộc tính này cũng sẽ xóa các giá trị của nó trong tất cả các dự án. Bạn có chắc chắn muốn làm điều này?" + text_are_you_sure_with_project_life_cycle_step: "Xóa giai đoạn này cũng sẽ xóa việc sử dụng nó trong tất cả các dự án. Bạn có chắc chắn muốn làm điều này?" + text_assign_to_project: "Giao cho dự án" text_form_configuration: > Bạn có thể tùy chỉnh các trường sẽ được hiển thị trong các gói công việc. Bạn có thể tự do nhóm các trường để phản ánh nhu cầu cho tên miền của bạn. text_form_configuration_required_attribute: "Thuộc tính được đánh dấu là bắt buộc và do đó luôn được hiển thị" - text_caracters_maximum: "Tối đa %{count} ký tự." - text_caracters_minimum: "Ít nhất phải có %{count} ký tự." - text_comma_separated: "Nhiều giá trị được phép (ngăn cách bằng dấu phẩy)." + text_caracters_maximum: "tối đa %{count} ký tự." + text_caracters_minimum: "Phải dài ít nhất %{count} ký tự." + text_comma_separated: "Cho phép nhiều giá trị (được phân tách bằng dấu phẩy)." text_comment_wiki_page: "Bình luận cho trang wiki: %{page}" text_custom_field_possible_values_info: "Một dòng cho mỗi giá trị" text_custom_field_hint_activate_per_project: > - Khi sử dụng các trường tùy chỉnh: Hãy nhớ rằng các trường tùy chỉnh cần được kích hoạt theo từng dự án. + Khi sử dụng trường tùy chỉnh: Hãy nhớ rằng các trường tùy chỉnh cũng cần được kích hoạt cho mỗi dự án. text_custom_field_hint_activate_per_project_and_type: > - Các trường tùy chỉnh cần được kích hoạt theo loại gói công việc và theo từng dự án. + Các trường tùy chỉnh cần được kích hoạt cho mỗi loại gói công việc và mỗi dự án. text_project_custom_field_html: > Phiên bản Enterprise sẽ thêm các phần bổ sung cho các trường tùy chỉnh của dự án:
    • Thêm các trường tùy chỉnh cho dự án vào danh sách Dự án của bạn để tạo cái nhìn tổng quan về danh mục dự án
    text_custom_logo_instructions: > - The logo automatically scales to fit the header. For best results, upload a white logo on a transparent 130×47px image. You can add as much spacing inside that image as you like. + Logo tự động chia tỷ lệ để vừa với tiêu đề. Để có kết quả tốt nhất, hãy tải lên biểu tượng màu trắng trên hình ảnh trong suốt có kích thước 130×47px. Bạn có thể thêm bao nhiêu khoảng cách bên trong hình ảnh đó tùy thích. text_custom_logo_mobile_instructions: > - The logo automatically scales to fit the header. For best results, upload a white logo on a transparent 130×33px image. You can add as much spacing inside that image as you like. + Logo tự động chia tỷ lệ để vừa với tiêu đề. Để có kết quả tốt nhất, hãy tải lên biểu tượng màu trắng trên hình ảnh trong suốt có kích thước 130×33px. Bạn có thể thêm bao nhiêu khoảng cách bên trong hình ảnh đó tùy thích. text_custom_export_logo_instructions: > - Đây là logo xuất hiện trong các bản xuất PDF của bạn. Nó cần phải là tệp hình ảnh PNG hoặc JPEG. Logo màu đen hoặc có màu trên nền trong suốt hoặc trắng được khuyến nghị. + Đây là logo xuất hiện trong bản xuất PDF của bạn. Nó cần phải là tệp hình ảnh PNG hoặc JPEG. Nên sử dụng biểu tượng màu đen hoặc màu trên nền trong suốt hoặc trắng. text_custom_export_cover_instructions: > - Đây là hình ảnh xuất hiện trên nền của trang bìa trong các bản xuất PDF của bạn. Nó cần phải là tệp hình ảnh PNG hoặc JPEG có kích thước khoảng 800px chiều rộng x 500px chiều cao. + Đây là hình ảnh xuất hiện dưới nền trang bìa trong bản xuất PDF của bạn. Nó cần phải có kích thước tệp hình ảnh PNG hoặc JPEG có chiều rộng khoảng 800px x chiều cao khoảng 500px. text_custom_export_footer_instructions: > - PDF exports will include a graphical element positioned to the left of the footer. This image must be a PNG or JPEG file with approximately 200 pixels in width. + Xuất PDF sẽ bao gồm một phần tử đồ họa được đặt ở bên trái chân trang. Hình ảnh này phải là tệp PNG hoặc JPEG có chiều rộng khoảng 200 pixel. label_custom_export_font_instructions: > - Upload and manage custom TrueType (.ttf) fonts used in your PDF exports. For best results, use matching files from the same font family. If no font is provided, the default NotoSans font will be used. + Tải lên và quản lý phông chữ TrueType (.ttf) tùy chỉnh được sử dụng trong bản xuất PDF của bạn. Để có kết quả tốt nhất, hãy sử dụng các tệp phù hợp từ cùng một họ phông chữ. Nếu không có phông chữ nào được cung cấp thì phông chữ NotoSans mặc định sẽ được sử dụng. label_custom_export_images_instructions: > - Upload and manage custom image files used in your PDF exports. + Tải lên và quản lý các tệp hình ảnh tùy chỉnh được sử dụng trong bản xuất PDF của bạn. text_custom_export_font_regular_instructions: > - This is the font file for regular text. It needs to be in TTF format and is required. + Đây là tập tin phông chữ cho văn bản thông thường. Nó cần phải ở định dạng TTF và được yêu cầu. text_custom_export_font_bold_instructions: > - This is the font file for bold text. It needs to be in TTF format. + Đây là tập tin phông chữ cho văn bản in đậm. Nó cần phải ở định dạng TTF. text_custom_export_font_italic_instructions: > - This is the font file for italic text. It needs to be in TTF format. + Đây là tập tin phông chữ cho văn bản in nghiêng. Nó cần phải ở định dạng TTF. text_custom_export_font_bold_italic_instructions: > - This is the font file for bold and italic text. It needs to be in TTF format. + Đây là tập tin phông chữ cho văn bản in đậm và in nghiêng. Nó cần phải ở định dạng TTF. text_custom_favicon_instructions: > - Đây là biểu tượng nhỏ xuất hiện trên cửa sổ/tab trình duyệt của bạn bên cạnh tiêu đề trang. Nó cần phải là tệp hình ảnh PNG có kích thước vuông 32 x 32 pixels với nền trong suốt. + Đây là biểu tượng nhỏ xuất hiện trong cửa sổ/tab trình duyệt của bạn bên cạnh tiêu đề trang. Nó phải là tệp hình ảnh PNG bình phương có kích thước 32 x 32 pixel với nền trong suốt. text_custom_touch_icon_instructions: > - Đây là biểu tượng xuất hiện trên thiết bị di động hoặc máy tính bảng của bạn khi bạn đặt một dấu trang trên màn hình chính của bạn. Nó cần phải là tệp hình ảnh PNG có kích thước vuông 180 x 180 pixels. Vui lòng đảm bảo nền của hình ảnh không trong suốt, nếu không nó sẽ trông không đẹp trên iOS. + Đây là biểu tượng xuất hiện trên điện thoại di động hoặc máy tính bảng của bạn khi bạn đặt dấu trang trên màn hình chính. Nó phải là tệp hình ảnh PNG có kích thước 180 x 180 pixel bình phương. Vui lòng đảm bảo nền của hình ảnh không trong suốt, nếu không nó sẽ trông xấu trên iOS. text_database_allows_tsv: "Tùy chọn cho phép TSVector của Database" text_default_administrator_account_changed: "Tài khoản quản trị viên mặc định đã thay đổi" text_default_encoding: "Mặc định: UTF-8" - text_destroy: "Xoá" - text_destroy_with_associated: "Có các đối tượng bổ sung liên kết với gói công việc(s) mà sẽ bị xóa. Các đối tượng đó thuộc loại sau:" + text_destroy: "xóa" + text_destroy_with_associated: "Có các đối tượng bổ sung liên quan đến (các) gói công việc sẽ bị xóa. Các đối tượng đó có các loại sau:" text_destroy_what_to_do: "Bạn muốn làm gì?" text_diff_truncated: "... Sự khác biệt này đã bị cắt bớt vì nó vượt quá kích thước tối đa có thể được hiển thị." text_email_delivery_not_configured: "Gửi email không được cấu hình và thông báo bị vô hiệu hóa.\nCấu hình máy chủ SMTP của bạn để kích hoạt chúng." - text_enumeration_category_reassign_to: "Gán lại cho giá trị này:" - text_enumeration_destroy_question: "%{count} đối tượng đã được gán cho giá trị này." - text_file_repository_writable: "Thư mục đính kèm có thể ghi được" - text_git_repo_example: "một kho lưu trữ bare và local (ví dụ: /gitrepo, c:\\gitrepo)" - text_hint_date_format: "Nhập một ngày theo định dạng YYYY-MM-DD. Các định dạng khác có thể được chuyển đổi thành ngày không mong muốn." - text_hint_disable_with_0: "Lưu ý: Vô hiệu hóa với 0" + text_enumeration_category_reassign_to: "Gán lại chúng cho giá trị này:" + text_enumeration_destroy_question: "Các đối tượng %{count} được gán cho giá trị này." + text_file_repository_writable: "Thư mục đính kèm có thể ghi" + text_git_repo_example: "một kho lưu trữ trống và cục bộ (trong /git repo, c:\\git repo)" + text_hint_date_format: "Nhập ngày ở dạng YYYY-MM-DD. Các định dạng khác có thể được thay đổi thành ngày không mong muốn." + text_hint_disable_with_0: "Lưu ý: Vô hiệu hóa bằng 0" text_hours_between: "Giữa %{min} và %{max} giờ." text_work_package_added: "Gói công việc %{id} đã được báo cáo bởi %{author}." - text_work_package_category_destroy_assignments: "Gỡ bỏ gán danh mục" - text_work_package_category_destroy_question: "Một số gói công việc (%{count}) đã được gán cho danh mục này. Bạn muốn làm gì?" + text_work_package_category_destroy_assignments: "Xóa bài tập danh mục" + text_work_package_category_destroy_question: "Một số gói công việc (%{count}) được gán cho danh mục này. Bạn muốn làm gì?" text_work_package_category_reassign_to: "Gán lại các gói công việc cho danh mục này" text_work_package_updated: "Gói công việc %{id} đã được cập nhật bởi %{author}." - text_work_package_watcher_added: "Bạn đã được thêm vào danh sách theo dõi Gói công việc %{id} bởi %{watcher_changer}." - text_work_package_watcher_removed: "Bạn đã bị xóa khỏi danh sách theo dõi Gói công việc %{id} bởi %{watcher_changer}." - text_work_packages_destroy_confirmation: "Bạn có chắc chắn muốn xóa các gói công việc(s) đã chọn không?" - text_work_packages_ref_in_commit_messages: "Tham chiếu và sửa các gói công việc trong các thông điệp cam kết" - text_journal_added: "%{label} %{value} đã được thêm" - text_journal_attachment_added: "%{label} %{value} đã được thêm dưới dạng đính kèm" - text_journal_attachment_deleted: "%{label} %{old} đã bị xóa dưới dạng đính kèm" - text_journal_changed_plain: "%{label} đã thay đổi từ %{old} %{linebreak} đến %{new}" - text_journal_changed_no_detail: "%{label} đã được cập nhật" + text_work_package_watcher_added: "Bạn đã được thêm làm người theo dõi gói Công việc %{id} bởi %{watcher_changer}." + text_work_package_watcher_removed: "Bạn đã bị %{watcher_changer} xóa khỏi danh sách người theo dõi gói Công việc %{id}." + text_work_packages_destroy_confirmation: "Bạn có chắc chắn muốn xóa (các) gói công việc đã chọn không?" + text_work_packages_ref_in_commit_messages: "Tham khảo và sửa các gói công việc trong thông báo cam kết" + text_journal_added: "%{label} %{value} đã thêm" + text_journal_attachment_added: "%{label} %{value} được thêm dưới dạng tệp đính kèm" + text_journal_attachment_deleted: "%{label} %{old} đã bị xóa dưới dạng tệp đính kèm" + text_journal_changed_plain: "%{label} đã thay đổi từ %{old} %{linebreak}thành %{new}" + text_journal_changed_no_detail: "%{label} đã cập nhật" text_journal_changed_with_diff: "%{label} đã thay đổi (%{link})" - text_journal_deleted: "%{label} đã bị xóa (%{old})" + text_journal_deleted: "%{label} đã xóa (%{old})" text_journal_deleted_subproject: "%{label} %{old}" - text_journal_deleted_with_diff: "%{label} đã bị xóa (%{link})" - text_journal_file_link_added: "%{label} liên kết đến %{value} (%{storage}) đã được thêm" - text_journal_file_link_deleted: "%{label} liên kết đến %{old} (%{storage}) đã bị xóa" + text_journal_deleted_with_diff: "%{label} đã xóa (%{link})" + text_journal_file_link_added: "Đã thêm %{label} liên kết tới %{value} (%{storage})" + text_journal_file_link_deleted: "%{label} liên kết tới %{old} (%{storage}) đã bị xóa" text_journal_of: "%{label} %{value}" - text_journal_set_to: "%{label} đã được đặt thành %{value}" - text_journal_set_with_diff: "%{label} đã được đặt (%{link})" + text_journal_set_to: "%{label} được đặt thành %{value}" + text_journal_set_with_diff: "%{label} đặt (%{link})" text_journal_label_value: "%{label} %{value}" - text_latest_note: "Nhận xét mới nhất là: %{note}" - text_length_between: "Độ dài giữa %{min} và %{max} ký tự." - text_line_separated: "Nhiều giá trị được phép (mỗi giá trị trên một dòng)." + text_latest_note: "Bình luận mới nhất là: %{note}" + text_length_between: "Độ dài từ %{min} đến %{max} ký tự." + text_line_separated: "Cho phép nhiều giá trị (mỗi giá trị một dòng)." text_load_default_configuration: "Tải cấu hình mặc định" - text_min_max_length_info: "0 có nghĩa là không có hạn chế" - text_no_roles_defined: Chưa có vai trò nào được định nghĩa. - text_no_access_tokens_configurable: "Chưa có mã truy cập nào có thể được cấu hình." - text_no_configuration_data: "Vai trò, loại, trạng thái gói công việc và quy trình làm việc chưa được cấu hình.\nRất khuyến khích bạn tải cấu hình mặc định. Bạn sẽ có thể chỉnh sửa sau khi tải xong." - text_no_notes: "Không có nhận xét nào cho gói công việc này." - text_notice_too_many_values_are_inperformant: "Lưu ý: Hiển thị hơn 100 mục mỗi trang có thể làm tăng thời gian tải trang." + text_min_max_length_info: "0 có nghĩa là không hạn chế" + text_no_roles_defined: Không có vai trò nào được xác định. + text_no_access_tokens_configurable: "Không có mã thông báo truy cập nào có thể được cấu hình." + text_no_configuration_data: "Vai trò, loại, trạng thái gói công việc và quy trình làm việc chưa được định cấu hình.\nRất khuyến khích tải cấu hình mặc định. Bạn sẽ có thể sửa đổi nó sau khi tải." + text_no_notes: "Không có bình luận nào cho gói công việc này." + text_notice_too_many_values_are_inperformant: "Lưu ý: Hiển thị hơn 100 mục trên mỗi trang có thể làm tăng thời gian tải trang." text_notice_security_badge_displayed_html: > Lưu ý: nếu được kích hoạt, một bảng thông báo về tình trạng của phần mềm sẽ được hiển thị tại trang quản trị %{information_panel_label}, và tại trang chủ. Thông tin này chỉ được thấy bởi Quản trị viên.
    Bảng thông báo sẽ kiểm tra các thông tin cập nhật từ OpenProject và thông báo về các cập nhật mới hay các lỗi được phát hiện. Để biết thêm chi tiết, vui lòng tham khảo tài liệu cấu hình. - text_own_membership_delete_confirmation: "Bạn sắp xóa một số hoặc tất cả quyền của mình và có thể không còn khả năng chỉnh sửa dự án này sau đó.\nBạn có chắc chắn muốn tiếp tục không?" - text_permanent_delete_confirmation_checkbox_label: "I understand that this deletion cannot be reversed" - text_permanent_remove_confirmation_checkbox_label: "I understand that this removal cannot be reversed" - text_plugin_assets_writable: "Thư mục tài sản plugin có thể ghi được" + text_own_membership_delete_confirmation: "Bạn sắp xóa một số hoặc tất cả các quyền của mình và sau đó có thể không chỉnh sửa được dự án này nữa.\nBạn có chắc chắn muốn tiếp tục không?" + text_permanent_delete_confirmation_checkbox_label: "Tôi hiểu rằng việc xóa này không thể đảo ngược" + text_permanent_remove_confirmation_checkbox_label: "Tôi hiểu rằng việc xóa này không thể hoàn tác được" + text_plugin_assets_writable: "Thư mục nội dung plugin có thể ghi" text_powered_by: "Được cung cấp bởi %{link}" - text_project_identifier_info: "Chỉ cho phép chữ cái viết thường (a-z), số, dấu gạch ngang và gạch dưới, phải bắt đầu bằng chữ cái viết thường." - text_reassign: "Gán lại cho gói công việc:" - text_regexp_info: "ví dụ: ^[A-Z0-9]+$" + text_project_identifier_info: "Chỉ cho phép chữ cái viết thường (a-z), số, dấu gạch ngang và dấu gạch dưới, phải bắt đầu bằng chữ cái viết thường." + text_reassign: "Phân công lại cho gói công việc:" + text_regexp_info: "ví dụ. ^[A-Z0-9]+$" text_regexp_multiline: 'Biểu thức được áp dụng ở chế độ đa dòng. Ví dụ: ^---\s+' - text_repository_usernames_mapping: "Chọn hoặc cập nhật người dùng OpenProject được ánh xạ với từng tên người dùng tìm thấy trong nhật ký kho lưu trữ.\nNgười dùng với cùng tên người dùng hoặc email OpenProject và kho lưu trữ sẽ được ánh xạ tự động." - text_status_changed_by_changeset: "Áp dụng trong thay đổi %{value}." - text_table_difference_description: "Trong bảng này, các mục đơn %{entries} được hiển thị. Bạn có thể xem sự khác biệt giữa bất kỳ hai mục nào bằng cách chọn các hộp kiểm tương ứng trong bảng trước. Khi nhấp vào nút bên dưới bảng, các sự khác biệt sẽ được hiển thị." - text_time_logged_by_changeset: "Áp dụng trong thay đổi %{value}." - text_tip_work_package_begin_day: "gói công việc bắt đầu vào ngày này" - text_tip_work_package_begin_end_day: "gói công việc bắt đầu và kết thúc vào ngày này" - text_tip_work_package_end_day: "gói công việc kết thúc vào ngày này" - text_type_no_workflow: "Không có quy trình làm việc được định nghĩa cho loại này" + text_repository_usernames_mapping: "Chọn hoặc cập nhật người dùng OpenProject được ánh xạ tới từng tên người dùng được tìm thấy trong nhật ký kho lưu trữ.\nNgười dùng có cùng tên người dùng hoặc email của OpenProject và kho lưu trữ sẽ được tự động ánh xạ." + text_status_changed_by_changeset: "Được áp dụng trong bộ thay đổi %{value}." + text_table_difference_description: "Trong bảng này, đơn %{entries} được hiển thị. Bạn có thể xem sự khác biệt giữa hai mục bất kỳ bằng cách trước tiên chọn các hộp kiểm tương ứng trong bảng. Khi nhấp vào nút bên dưới bảng, sự khác biệt sẽ được hiển thị." + text_time_logged_by_changeset: "Được áp dụng trong bộ thay đổi %{value}." + text_tip_work_package_begin_day: "gói công việc bắt đầu từ ngày hôm nay" + text_tip_work_package_begin_end_day: "gói công việc bắt đầu và kết thúc ngày hôm nay" + text_tip_work_package_end_day: "gói công việc kết thúc vào ngày hôm nay" + text_type_no_workflow: "Không có quy trình làm việc nào được xác định cho loại này" text_unallowed_characters: "Ký tự không được phép" text_user_invited: Người dùng đã được mời và đang chờ đăng ký. text_user_wrote: "%{value} đã viết:" - text_wrote: "wrote" + text_wrote: "đã viết" text_warn_on_leaving_unsaved: "Gói công việc chứa văn bản chưa được lưu sẽ bị mất nếu bạn rời khỏi trang này." text_what_did_you_change_click_to_add_comment: "Bạn đã thay đổi gì? Kích để thêm ghi chú" text_wiki_destroy_confirmation: "Bạn có chắc chắn muốn xóa wiki này và tất cả nội dung của nó không?" - text_wiki_page_destroy_children: "Xóa các trang con và tất cả các hậu duệ của chúng" - text_wiki_page_destroy_question: "Trang này có %{descendants} trang con(s) và trang cháu(s). Bạn muốn làm gì?" - text_wiki_page_nullify_children: "Giữ các trang con như các trang gốc" - text_wiki_page_reassign_children: "Gán lại các trang con cho trang cha này" - text_workflow_edit: "Chọn một vai trò và một loại để chỉnh sửa quy trình làm việc" + text_wiki_page_destroy_children: "Xóa các trang con và tất cả các trang con của chúng" + text_wiki_page_destroy_question: "Trang này có %{descendants} trang con và trang con. Bạn muốn làm gì?" + text_wiki_page_nullify_children: "Giữ các trang con làm trang gốc" + text_wiki_page_reassign_children: "Gán lại các trang con cho trang mẹ này" + text_workflow_edit: "Chọn vai trò và loại để chỉnh sửa quy trình làm việc" text_zoom_in: "Phóng to" text_zoom_out: "Thu nhỏ" text_setup_mail_configuration: "Cấu hình nhà cung cấp email của bạn" help_texts: views: project: > - %{plural} luôn gắn liền với một dự án. Bạn chỉ có thể chọn các dự án ở đây mà mô-đun %{plural} đang hoạt động. Sau khi tạo một %{singular}, bạn có thể thêm các gói công việc từ các dự án khác vào nó. - public: "Xuất bản chế độ xem này, cho phép người dùng khác truy cập chế độ xem của bạn. Người dùng có quyền 'Quản lý các chế độ xem công khai' có thể chỉnh sửa hoặc xóa truy vấn công khai. Điều này không ảnh hưởng đến khả năng hiển thị của kết quả gói công việc trong chế độ xem đó và tùy thuộc vào quyền của họ, người dùng có thể thấy các kết quả khác nhau." - favoured: "Đánh dấu chế độ xem này là yêu thích và thêm vào thanh bên các chế độ xem đã lưu ở bên trái." + %{plural} luôn gắn liền với một dự án. Ở đây bạn chỉ có thể chọn các dự án có mô-đun %{plural} đang hoạt động. Sau khi tạo %{singular} bạn có thể thêm các gói công việc từ các dự án khác vào đó. + public: "Xuất bản chế độ xem này, cho phép người dùng khác truy cập vào chế độ xem của bạn. Người dùng có quyền 'Quản lý chế độ xem công khai' có thể sửa đổi hoặc xóa truy vấn công khai. Điều này không ảnh hưởng đến khả năng hiển thị của kết quả gói công việc trong chế độ xem đó và tùy thuộc vào quyền của họ, người dùng có thể thấy các kết quả khác nhau." + favoured: "Đánh dấu chế độ xem này là yêu thích và thêm vào thanh bên chế độ xem đã lưu ở bên trái." time: am: "sáng" formats: @@ -4792,142 +4790,142 @@ vi: show: "Hiển thị khung thời gian" end: "đến" start: "từ" - title_remove_and_delete_user: Xóa người dùng đã mời khỏi dự án và xóa người đó. + title_remove_and_delete_user: Xóa người dùng được mời khỏi dự án và xóa anh ấy/cô ấy. title_enterprise_upgrade: "Nâng cấp để mở khóa nhiều người dùng hơn." tooltip_user_default_timezone: > Time zone mặc định, không thể thay bởi thiết lapạ Nsd. tooltip_resend_invitation: > - Gửi một email mời khác với mã mới trong trường hợp mã cũ đã hết hạn hoặc người dùng chưa nhận được email gốc. Cũng có thể được sử dụng cho người dùng đang hoạt động để chọn phương thức xác thực mới. Khi được sử dụng với người dùng đang hoạt động, trạng thái của họ sẽ được thay đổi thành 'đã mời'. + Gửi một email mời khác kèm theo mã thông báo mới trong trường hợp email cũ hết hạn hoặc người dùng không nhận được email gốc. Cũng có thể được sử dụng để người dùng đang hoạt động chọn phương thức xác thực mới. Khi được sử dụng với người dùng đang hoạt động, trạng thái của họ sẽ được thay đổi thành 'được mời'. tooltip: setting_email_login: > - Nếu được kích hoạt, người dùng sẽ không thể chọn đăng nhập trong quá trình đăng ký. Thay vào đó, địa chỉ email của họ sẽ được sử dụng làm đăng nhập. Một quản trị viên vẫn có thể thay đổi đăng nhập một cách riêng biệt. + Nếu được bật, người dùng sẽ không thể chọn đăng nhập trong khi đăng ký. Thay vào đó, địa chỉ email đã cung cấp của họ sẽ dùng làm thông tin đăng nhập. Quản trị viên vẫn có thể thay đổi thông tin đăng nhập riêng. queries: - apply_filter: Áp dụng bộ lọc đã cấu hình sẵn + apply_filter: Áp dụng bộ lọc được cấu hình sẵn configure_view: - heading: Cấu hình chế độ xem + heading: Định cấu hình chế độ xem columns: input_label: "Thêm cột" input_placeholder: "Chọn một cột" drag_area_label: "Quản lý và sắp xếp lại các cột" sort_by: automatic: - heading: "Tự động" - description: "Sắp xếp %{plural} theo một hoặc nhiều tiêu chí sắp xếp. Bạn sẽ mất sắp xếp trước đó." + heading: "tự động" + description: "Sắp xếp %{plural} theo một hoặc nhiều tiêu chí sắp xếp. Bạn sẽ mất việc sắp xếp trước đó." top_menu: additional_resources: "Tài nguyên bổ sung" getting_started: "Bắt đầu" help_and_support: "Trợ giúp và hỗ trợ" - total_progress: "Tiến độ tổng thể" + total_progress: "Tổng tiến độ" user: all: "tất cả" - active: "đang hoạt động" - activate: "Kích hoạt" - activate_and_reset_failed_logins: "Kích hoạt và đặt lại các lần đăng nhập không thành công" + active: "Đang hoạt động" + activate: "kích hoạt" + activate_and_reset_failed_logins: "Kích hoạt và đặt lại thông tin đăng nhập thất bại" authentication_provider: "Nhà cung cấp xác thực" - identity_url_text: "Mã định danh duy nhất nội bộ do nhà cung cấp xác thực cung cấp." + identity_url_text: "Mã định danh duy nhất nội bộ do nhà cung cấp dịch vụ xác thực cung cấp." authentication_settings_disabled_due_to_external_authentication: > - Người dùng này xác thực qua nhà cung cấp xác thực bên ngoài, vì vậy không có mật khẩu trong OpenProject để thay đổi. + Người dùng này xác thực thông qua nhà cung cấp xác thực bên ngoài, do đó không có mật khẩu nào trong OpenProject cần thay đổi. authorization_rejected: "Bạn không được phép đăng nhập." - assign_random_password: "Gán mật khẩu ngẫu nhiên (gửi qua email cho người dùng)" - blocked: "khóa tạm thời" + assign_random_password: "Gán mật khẩu ngẫu nhiên (gửi cho người dùng qua email)" + blocked: "bị khóa tạm thời" blocked_num_failed_logins: other: "bị khóa tạm thời (%{count} lần đăng nhập không thành công)" - confirm_status_change: "Bạn sắp thay đổi trạng thái của '%{name}'. Bạn có chắc chắn muốn tiếp tục không?" + confirm_status_change: "Bạn sắp thay đổi trạng thái '%{name}'. Bạn có chắc chắn muốn tiếp tục không?" deleted: "Người dùng đã bị xóa" - error_status_change_self: "You cannot change your own user status." - error_admin_change_on_non_admin: "Only administrators can change the status of administrator users." + error_status_change_self: "Bạn không thể thay đổi trạng thái người dùng của riêng bạn." + error_admin_change_on_non_admin: "Chỉ quản trị viên mới có thể thay đổi trạng thái của người dùng quản trị viên." error_status_change_failed: "Thay đổi trạng thái người dùng không thành công do các lỗi sau: %{errors}" invite: Mời người dùng qua email - invited: được mời + invited: Đã mời lock: "Khóa vĩnh viễn" - locked: "khóa vĩnh viễn" - no_login: "Người dùng này xác thực qua đăng nhập bằng mật khẩu. Vì nó đã bị vô hiệu hóa, họ không thể đăng nhập." + locked: "bị khóa vĩnh viễn" + no_login: "Người dùng này xác thực thông qua đăng nhập bằng mật khẩu. Vì nó bị vô hiệu hóa nên họ không thể đăng nhập." password_change_unsupported: Thay đổi mật khẩu không được hỗ trợ. registered: "đã đăng ký" - reset_failed_logins: "Đặt lại các lần đăng nhập không thành công" + reset_failed_logins: "Đặt lại thông tin đăng nhập thất bại" status_user_and_brute_force: "%{user} và %{brute_force}" status_change: "Thay đổi trạng thái" text_change_disabled_for_provider_login: "Tên được thiết lập với người cung cấp đăng nhập của bạn và vì thế không thể thay đổi" - text_change_disabled_for_ldap_login: "Tên và email được thiết lập bởi LDAP và do đó không thể thay đổi." + text_change_disabled_for_ldap_login: "Tên và email do LDAP đặt và do đó không thể thay đổi." unlock: "Mở khoá" - unlock_and_reset_failed_logins: "Mở khóa và đặt lại các lần đăng nhập không thành công" - version_status_closed: "đã đóng" - version_status_locked: "khóa" + unlock_and_reset_failed_logins: "Mở khóa và đặt lại thông tin đăng nhập thất bại" + version_status_closed: "đóng cửa" + version_status_locked: "bị khóa" version_status_open: "mở" - note: Ghi chú + note: Lưu ý note_password_login_disabled: "Đăng nhập bằng mật khẩu đã bị vô hiệu hóa bởi %{configuration}." warning: Cảnh báo - warning_attachments_not_saved: "Không thể lưu %{count} tệp(s)." + warning_attachments_not_saved: "%{count} tệp không thể lưu được." warning_imminent_user_limit: > Bạn đã mời nhiều người dùng hơn số lượng được hỗ trợ bởi gói hiện tại của bạn. Những người dùng được mời có thể không thể tham gia vào môi trường OpenProject của bạn. Vui lòng nâng cấp gói của bạn hoặc khóa người dùng hiện tại để cho phép người dùng được mời và đã đăng ký tham gia. warning_registration_token_expired: | Mail kích hoạt đã hết hạn. Sẽ gửi mail mới tới %{email}. warning_user_limit_reached: > - Thêm người dùng bổ sung sẽ vượt quá giới hạn hiện tại. Vui lòng liên hệ với quản trị viên để tăng giới hạn người dùng để đảm bảo người dùng bên ngoài có thể truy cập vào phiên bản này. + Việc thêm người dùng bổ sung sẽ vượt quá giới hạn hiện tại. Vui lòng liên hệ với quản trị viên để tăng giới hạn người dùng nhằm đảm bảo người dùng bên ngoài có thể truy cập phiên bản này. warning_user_limit_reached_admin: > Thêm người dùng bổ sung sẽ vượt quá giới hạn hiện tại. Vui lòng nâng cấp gói của bạn để đảm bảo người dùng bên ngoài có thể truy cập vào phiên bản này. warning_user_limit_reached_instructions: > - Bạn đã đạt đến giới hạn người dùng của mình (%{current}/%{max} người dùng hoạt động). Vui lòng liên hệ với sales@openproject.com để nâng cấp gói Enterprise của bạn và thêm người dùng bổ sung. + Bạn đã đạt đến giới hạn người dùng của mình (%{current}/%{max} người dùng đang hoạt động). Vui lòng liên hệ sales@openproject.com để nâng cấp gói phiên bản Enterprise của bạn và thêm người dùng bổ sung. warning_protocol_mismatch_html: > warning_bar: https_mismatch: - title: "Không khớp cấu hình chế độ HTTPS" + title: "Thiết lập chế độ HTTPS không khớp" text_html: > Ứng dụng của bạn đang chạy với chế độ HTTPS được đặt thành %{set_protocol}, nhưng yêu cầu là một yêu cầu %{actual_protocol}. Điều này sẽ gây ra lỗi! Bạn sẽ cần thiết lập giá trị cấu hình sau: %{setting_value}. Vui lòng xem tài liệu cài đặt về cách thiết lập cấu hình này. hostname_mismatch: title: "Giao thức cài đặt không khớp" text_html: > Ứng dụng của bạn đang chạy với cài đặt tên máy chủ được đặt thành %{set_hostname}, nhưng yêu cầu là %{actual_hostname} máy chủ . Điều này sẽ dẫn đến lỗi! Truy cập Cài đặt hệ thống và thay đổi cài đặt "Tên máy chủ" để sửa lỗi này. - menu_item: "Mục menu" - menu_item_setting: "Hiển thị" + menu_item: "Mục thực đơn" + menu_item_setting: "Khả năng hiển thị" wiki_menu_item_for: 'Mục menu cho trang wiki "%{title}"' - wiki_menu_item_setting: "Hiển thị" + wiki_menu_item_setting: "Khả năng hiển thị" wiki_menu_item_new_main_item_explanation: > - Bạn đang xóa mục menu chính wiki duy nhất. Bạn cần chọn một trang wiki để tạo một mục chính mới. Để xóa wiki, mô-đun wiki có thể được vô hiệu hóa bởi các quản trị viên dự án. + Bạn đang xóa mục menu wiki chính duy nhất. Bây giờ bạn phải chọn một trang wiki để tạo một mục chính mới. Để xóa wiki, quản trị viên dự án có thể vô hiệu hóa mô-đun wiki. wiki_menu_item_delete_not_permitted: Không thể xóa mục menu wiki của trang wiki duy nhất. #TODO: merge with work_packages top level key work_package: updated_automatically_by_child_changes: | - _Được cập nhật tự động khi thay đổi giá trị trong gói công việc con %{child}_ + _Được cập nhật tự động bằng cách thay đổi các giá trị trong gói công việc con %{child}_ destroy: - info: "Xóa gói công việc là hành động không thể hoàn tác." + info: "Xóa gói công việc là một hành động không thể thay đổi được." title: "Xóa gói công việc" progress: label_note: "Ghi chú:" modal: - work_based_help_text: "Mỗi trường sẽ được tự động tính toán dựa trên hai trường còn lại khi có thể." - status_based_help_text: "% Hoàn thành được thiết lập theo trạng thái của gói công việc." - migration_warning_text: "Trong chế độ tính toán tiến độ dựa trên công việc, % Hoàn thành không thể được đặt thủ công và liên kết với Công việc. Giá trị hiện tại đã được giữ nhưng không thể chỉnh sửa. Vui lòng nhập Công việc trước." + work_based_help_text: "Mỗi trường được tự động tính toán từ hai trường còn lại khi có thể." + status_based_help_text: "% Hoàn thành được đặt theo trạng thái gói công việc." + migration_warning_text: "Trong chế độ tính toán tiến độ dựa trên công việc, % Hoàn thành không thể được đặt thủ công và được gắn với Công việc. Giá trị hiện tại đã được giữ nhưng không thể chỉnh sửa. Vui lòng nhập Công việc trước." derivation_hints: done_ratio: - cleared_because_remaining_work_is_empty: "Đã xóa vì công việc còn lại đang trống." + cleared_because_remaining_work_is_empty: "Đã xóa vì Công việc còn lại trống." cleared_because_work_is_0h: "Đã xóa vì Công việc là 0h." - derived: "Được suy ra từ Công và Công còn lại." + derived: "Bắt nguồn từ Công việc và Công việc còn lại." estimated_hours: - cleared_because_remaining_work_is_empty: "Đã xóa vì công việc còn lại đang trống." - derived: "Được lấy từ Công việc còn lại và % Hoàn thành." - same_as_remaining_work: "Đặt thành giá trị giống với Công việc còn lại." + cleared_because_remaining_work_is_empty: "Đã xóa vì Công việc còn lại trống." + derived: "Bắt nguồn từ Công việc còn lại và % Hoàn thành." + same_as_remaining_work: "Đặt thành cùng giá trị với Công việc còn lại." remaining_hours: - cleared_because_work_is_empty: "Đã xóa vì Công việc đang trống." - cleared_because_percent_complete_is_empty: "Đã xóa vì % Hoàn thành đang trống." - decreased_by_delta_like_work: "Giảm %{delta}, phù hợp với mức giảm trong Công việc." - derived: "Được lấy từ Công việc và % Hoàn thành." - increased_by_delta_like_work: "Tăng thêm %{delta}, tương ứng với mức tăng của Công việc." - same_as_work: "Đặt thành giá trị giống như Work." + cleared_because_work_is_empty: "Đã xóa vì Công việc trống." + cleared_because_percent_complete_is_empty: "Đã xóa vì % Hoàn thành trống." + decreased_by_delta_like_work: "Đã giảm %{delta}, phù hợp với mức giảm Công việc." + derived: "Bắt nguồn từ Công việc và % Hoàn thành." + increased_by_delta_like_work: "Tăng thêm %{delta}, tương ứng với mức tăng trong Công việc." + same_as_work: "Đặt thành cùng giá trị với Công việc." permissions: - comment: "Nhận xét" + comment: "bình luận" comment_description: "Có thể xem và nhận xét gói công việc này." edit: "Chỉnh sửa" - edit_description: "Có thể xem, nhận xét và chỉnh sửa gói công việc này." - view: "Khung nhìn" + edit_description: "Có thể xem, bình luận và chỉnh sửa gói công việc này." + view: "lượt xem" view_description: "Có thể xem gói công việc này." reminders: - label_remind_at: "Ngày" - note_placeholder: "Why are you setting this reminder?" - create_success_message: "Reminder set successfully. You will receive a notification for this work package %{reminder_time}." - success_update_message: "Reminder updated successfully." - success_deletion_message: "Reminder deleted successfully." + label_remind_at: "ngày" + note_placeholder: "Tại sao bạn đặt lời nhắc này?" + create_success_message: "Đã đặt lời nhắc thành công. Bạn sẽ nhận được thông báo về gói công việc này %{reminder_time}." + success_update_message: "Đã cập nhật lời nhắc thành công." + success_deletion_message: "Đã xóa lời nhắc thành công." sharing: count: zero: "0 người dùng" @@ -4938,145 +4936,145 @@ vi: not_project_member: "Không phải thành viên dự án" project_group: "Nhóm dự án" not_project_group: "Không phải nhóm dự án" - user: "Người dùng" - group: "Nhóm" - role: "Vai trò" - type: "Kiểu" + user: "người dùng" + group: "nhóm" + role: "vai trò" + type: "loại" denied: "Bạn không có quyền chia sẻ %{entities}." label_search: "Tìm kiếm người dùng để mời" label_search_placeholder: "Tìm kiếm theo người dùng hoặc địa chỉ email" - label_toggle_all: "Chuyển đổi tất cả chia sẻ" + label_toggle_all: "Chuyển đổi tất cả các chia sẻ" remove: "Xóa" - share: "Chia sẻ" + share: "chia sẻ" text_empty_search_description: "Không có người dùng nào với tiêu chí lọc hiện tại." - text_empty_search_header: "Chúng tôi không tìm thấy kết quả phù hợp." - text_empty_state_description: "Mới đây %{entity} chưa được chia sẻ với bất kỳ ai." + text_empty_search_header: "Chúng tôi không thể tìm thấy bất kỳ kết quả phù hợp." + text_empty_state_description: "%{entity} chưa được chia sẻ với bất kỳ ai." text_empty_state_header: "Không được chia sẻ" - text_user_limit_reached: "Thêm người dùng bổ sung sẽ vượt quá giới hạn hiện tại. Vui lòng liên hệ với quản trị viên để tăng giới hạn người dùng để đảm bảo người dùng bên ngoài có thể truy cập vào %{entity} này." + text_user_limit_reached: "Việc thêm người dùng bổ sung sẽ vượt quá giới hạn hiện tại. Vui lòng liên hệ với quản trị viên để tăng giới hạn người dùng nhằm đảm bảo người dùng bên ngoài có thể truy cập %{entity} này." text_user_limit_reached_admins: 'Thêm người dùng bổ sung sẽ vượt quá giới hạn hiện tại. Vui lòng nâng cấp gói của bạn để có thể thêm nhiều người dùng hơn.' warning_user_limit_reached: > - Thêm người dùng bổ sung sẽ vượt quá giới hạn hiện tại. Vui lòng liên hệ với quản trị viên để tăng giới hạn người dùng để đảm bảo người dùng bên ngoài có thể truy cập vào %{entity} này. + Việc thêm người dùng bổ sung sẽ vượt quá giới hạn hiện tại. Vui lòng liên hệ với quản trị viên để tăng giới hạn người dùng nhằm đảm bảo người dùng bên ngoài có thể truy cập %{entity} này. warning_user_limit_reached_admin: > Thêm người dùng bổ sung sẽ vượt quá giới hạn hiện tại. Vui lòng nâng cấp gói của bạn để đảm bảo người dùng bên ngoài có thể truy cập vào %{entity} này. - warning_no_selected_user: "Vui lòng chọn người dùng để chia sẻ %{entity} này" - warning_locked_user: "Người dùng %{user} đã bị khóa và không thể chia sẻ" + warning_no_selected_user: "Vui lòng chọn người dùng để chia sẻ %{entity} này với" + warning_locked_user: "Người dùng %{user} đã bị khóa và không thể chia sẻ với" user_details: locked: "Người dùng bị khóa" - invited: "Lời mời đã được gửi. " + invited: "Đã gửi lời mời." resend_invite: "Gửi lại." invite_resent: "Lời mời đã được gửi lại" - not_project_member: "Không phải thành viên dự án" - project_group: "Các thành viên nhóm có thể có quyền bổ sung (như thành viên dự án)" + not_project_member: "Không phải là thành viên dự án" + project_group: "Thành viên nhóm có thể có các đặc quyền bổ sung (với tư cách là thành viên dự án)" not_project_group: "Nhóm (chia sẻ với tất cả các thành viên)" - additional_privileges_project: "Có thể có quyền bổ sung (như thành viên dự án)" - additional_privileges_group: "Có thể có quyền bổ sung (như thành viên nhóm)" - additional_privileges_project_or_group: "Có thể có quyền bổ sung (như thành viên dự án hoặc nhóm)" + additional_privileges_project: "Có thể có các đặc quyền bổ sung (với tư cách là thành viên dự án)" + additional_privileges_group: "Có thể có thêm đặc quyền (với tư cách là thành viên nhóm)" + additional_privileges_project_or_group: "Có thể có các đặc quyền bổ sung (với tư cách là thành viên dự án hoặc nhóm)" project_queries: publishing_denied: "Bạn không có quyền công khai danh sách dự án." - access_warning: "Người dùng chỉ thấy các dự án mà họ có quyền truy cập. Chia sẻ danh sách dự án không ảnh hưởng đến quyền truy cập của từng dự án." + access_warning: "Người dùng sẽ chỉ nhìn thấy các dự án mà họ có quyền truy cập. Việc chia sẻ danh sách dự án không ảnh hưởng đến quyền của từng dự án." user_details: - owner: "Chủ danh sách" - can_view_because_public: "Có thể xem vì danh sách được chia sẻ với mọi người" + owner: "Chủ sở hữu danh sách" + can_view_because_public: "Đã có thể xem vì danh sách được chia sẻ với mọi người" can_manage_public_lists: "Có thể chỉnh sửa do quyền toàn cầu" public_flag: - label: "Chia sẻ với tất cả mọi người tại %{instance_name}" + label: "Chia sẻ với mọi người tại %{instance_name}" caption: "Mọi người đều có thể xem danh sách dự án này. Những người có quyền chỉnh sửa toàn cầu có thể sửa đổi nó." blank_state: public: - header: "Chia sẻ với tất cả mọi người" - description: "Mọi người đều có thể xem danh sách dự án này. Bạn cũng có thể thêm người dùng cá nhân với quyền bổ sung." + header: "Được chia sẻ với mọi người" + description: "Mọi người đều có thể xem danh sách dự án này. Bạn cũng có thể thêm người dùng cá nhân với các quyền bổ sung." private: - header: "Chưa chia sẻ: Riêng tư" - description: "Danh sách dự án này chưa được chia sẻ với bất kỳ ai. Chỉ bạn có thể truy cập danh sách này." + header: "Không được chia sẻ: Riêng tư" + description: "Danh sách dự án này chưa được chia sẻ với bất kỳ ai. Chỉ có bạn mới có thể truy cập danh sách này." permissions: - view: "Khung nhìn" + view: "lượt xem" view_description: "Có thể xem danh sách dự án này." edit: "Chỉnh sửa" edit_description: "Có thể xem, chia sẻ và chỉnh sửa danh sách dự án này." upsell: - message: "Chia sẻ danh sách dự án với người dùng cá nhân là một tiện ích bổ sung doanh nghiệp." + message: "Chia sẻ danh sách dự án với người dùng cá nhân là một tiện ích bổ sung dành cho doanh nghiệp." working_days: info: > - Days that are not selected are skipped when scheduling work packages and project life cycles (and not included in the day count). These can be overridden at a work-package level. + Những ngày không được chọn sẽ bị bỏ qua khi lên lịch các gói công việc và vòng đời dự án (và không được tính vào số ngày). Chúng có thể được ghi đè ở cấp gói công việc. instance_wide_info: > - Các ngày được thêm vào danh sách dưới đây được coi là không làm việc và bị bỏ qua khi lập kế hoạch gói công việc. + Những ngày được thêm vào danh sách dưới đây được coi là không hoạt động và bị bỏ qua khi lên lịch các gói công việc. change_button: "Thay đổi ngày làm việc" warning: > - Changing which days of the week are considered working days or non-working days can affect the start and finish days of all work packages and life cycles in all projects in this instance. + Việc thay đổi ngày trong tuần được coi là ngày làm việc hay ngày không làm việc có thể ảnh hưởng đến ngày bắt đầu và ngày kết thúc của tất cả các gói công việc và vòng đời trong tất cả các dự án trong trường hợp này. journal_note: changed: _**Ngày làm việc** đã thay đổi (%{changes})._ days: - working: "%{day} hiện là ngày làm việc" - non_working: "%{day} hiện không phải là ngày làm việc" + working: "%{day} hiện đang hoạt động" + non_working: "%{day} hiện không hoạt động" dates: - working: "%{date} hiện là ngày làm việc" - non_working: "%{date} hiện không phải là ngày làm việc" + working: "%{date} hiện đang hoạt động" + non_working: "%{date} hiện không hoạt động" nothing_to_preview: "Không có gì để xem trước" api_v3: attributes: - property: "Thuộc tính" + property: "tài sản" errors: code_400: "Yêu cầu không hợp lệ: %{message}" - code_401: "Bạn cần xác thực để truy cập tài nguyên này." + code_401: "Bạn cần phải được xác thực để truy cập tài nguyên này." code_401_wrong_credentials: "Bạn đã không cung cấp thông tin xác thực chính xác." code_403: "Bạn không được phép truy cập tài nguyên này." - code_404: "Tài nguyên yêu cầu không thể tìm thấy." - code_409: "Không thể cập nhật tài nguyên do xung đột chỉnh sửa." + code_404: "Không thể tìm thấy tài nguyên được yêu cầu." + code_409: "Không thể cập nhật tài nguyên do các sửa đổi xung đột." code_429: "Quá nhiều yêu cầu. Vui lòng thử lại sau." code_500: "Đã xảy ra lỗi nội bộ." - code_500_outbound_request_failure: "Yêu cầu ra ngoài tới tài nguyên khác đã thất bại với mã trạng thái %{status_code}." - code_500_missing_enterprise_token: "Yêu cầu không thể được xử lý do token Doanh nghiệp không hợp lệ hoặc bị thiếu." + code_500_outbound_request_failure: "Yêu cầu gửi đi tới tài nguyên khác không thành công với mã trạng thái %{status_code}." + code_500_missing_enterprise_token: "Không thể xử lý yêu cầu do mã thông báo Doanh nghiệp không hợp lệ hoặc bị thiếu." bad_request: - emoji_reactions_activity_type_not_supported: "This activity type does not support emoji reactions." - invalid_link: "The link under key '%{key}' is not valid." - links_not_an_object: "_links must be a JSON object." + emoji_reactions_activity_type_not_supported: "Loại hoạt động này không hỗ trợ phản ứng bằng biểu tượng cảm xúc." + invalid_link: "Liên kết dưới khóa '%{key}' không hợp lệ." + links_not_an_object: "_links phải là một đối tượng JSON." conflict: multiple_reminders_not_allowed: |- - You can only set one reminder at a time for a work package. Please delete or update the existing reminder. + Bạn chỉ có thể đặt một lời nhắc mỗi lần cho gói công việc. Vui lòng xóa hoặc cập nhật lời nhắc hiện có. not_found: - work_package: "Gói công việc bạn tìm kiếm không thể tìm thấy hoặc đã bị xóa." - reminder: "The reminder you are looking for cannot be found or has been deleted." + work_package: "Không thể tìm thấy gói công việc bạn đang tìm kiếm hoặc đã bị xóa." + reminder: "Lời nhắc bạn đang tìm kiếm không thể tìm thấy hoặc đã bị xóa." expected: date: "YYYY-MM-DD (chỉ nhận ISO 8601)" - datetime: "YYYY-MM-DDThh:mm:ss[.lll][+hh:mm] (bất kỳ ngày giờ ISO 8601 nào)" - duration: "Thời gian ISO 8601" - invalid_content_type: "Mong đợi CONTENT-TYPE là '%{content_type}' nhưng nhận được '%{actual}'." - invalid_format: "Định dạng không hợp lệ cho thuộc tính '%{property}': Mong đợi định dạng như '%{expected_format}', nhưng nhận được '%{actual}'." - invalid_json: "Yêu cầu không thể được phân tích cú pháp dưới dạng JSON." + datetime: "YYYY-MM-DDThh:mm:ss[.lll][+hh:mm] (bất kỳ ngày giờ ISO 8601 tương thích nào)" + duration: "Thời lượng ISO 8601" + invalid_content_type: "Dự kiến ​​CONTENT-TYPE là '%{content_type}' nhưng lại nhận được '%{actual}'." + invalid_format: "Định dạng không hợp lệ cho thuộc tính '%{property}': Định dạng dự kiến ​​là '%{expected_format}' nhưng lại có '%{actual}'." + invalid_json: "Không thể phân tích cú pháp yêu cầu dưới dạng JSON." invalid_relation: "Mối quan hệ không hợp lệ." - invalid_resource: "Đối với thuộc tính '%{property}', một liên kết như '%{expected}' được mong đợi, nhưng nhận được '%{actual}'." + invalid_resource: "Đối với thuộc tính '%{property}', dự kiến ​​sẽ có một liên kết như '%{expected}' nhưng lại nhận được '%{actual}'." invalid_signal: - embed: "Nhúng yêu cầu của %{invalid} không được hỗ trợ. Các nhúng được hỗ trợ là %{supported}." - select: "Chọn yêu cầu của %{invalid} không được hỗ trợ. Các chọn được hỗ trợ là %{supported}." + embed: "Việc nhúng %{invalid} được yêu cầu không được hỗ trợ. Các phần nhúng được hỗ trợ là %{supported}." + select: "Việc chọn %{invalid} được yêu cầu không được hỗ trợ. Các lựa chọn được hỗ trợ là %{supported}." invalid_user_status_transition: "Trạng thái tài khoản người dùng hiện tại không cho phép thao tác này." - missing_content_type: "chưa được chỉ định" + missing_content_type: "không được chỉ định" missing_property: "Thiếu thuộc tính '%{property}'." - missing_request_body: "Không có phần thân yêu cầu." - missing_or_malformed_parameter: "Tham số truy vấn '%{parameter}' bị thiếu hoặc định dạng không đúng." - multipart_body_error: "Phần thân yêu cầu không chứa các phần multipart mong đợi." + missing_request_body: "Không có nội dung yêu cầu." + missing_or_malformed_parameter: "Tham số truy vấn '%{parameter}' bị thiếu hoặc không đúng định dạng." + multipart_body_error: "Nội dung yêu cầu không chứa các phần gồm nhiều phần như mong đợi." multiple_errors: "Nhiều ràng buộc trường đã bị vi phạm." - unable_to_create_attachment: "Tệp đính kèm không thể được tạo" - unable_to_create_attachment_permissions: "Tệp đính kèm không thể được lưu do thiếu quyền hệ thống tập tin" + unable_to_create_attachment: "Không thể tạo tệp đính kèm" + unable_to_create_attachment_permissions: "Không thể lưu tệp đính kèm do thiếu quyền hệ thống tệp" user: - name_readonly: "The name attribute is read-only. Changes can be written through the attributes firstname and lastname." + name_readonly: "Thuộc tính tên chỉ đọc. Các thay đổi có thể được viết thông qua các thuộc tính firstname và Lastname." render: - context_not_parsable: "Bối cảnh cung cấp không phải là liên kết đến tài nguyên." - unsupported_context: "Tài nguyên được cung cấp không được hỗ trợ làm bối cảnh." + context_not_parsable: "Bối cảnh được cung cấp không phải là một liên kết đến một tài nguyên." + unsupported_context: "Tài nguyên đã cho không được hỗ trợ dưới dạng ngữ cảnh." context_object_not_found: "Không thể tìm thấy tài nguyên được cung cấp làm bối cảnh." validation: - due_date: "Ngày hoàn thành không thể được đặt trên các gói công việc cha." + due_date: "Không thể đặt ngày kết thúc trên các gói công việc gốc." invalid_user_assigned_to_work_package: "Người dùng được chọn không được phép là '%{property}' cho gói công việc này." - start_date: "Ngày bắt đầu không thể được đặt trên các gói công việc cha." + start_date: "Không thể đặt ngày bắt đầu trên các gói công việc gốc." eprops: - invalid_gzip: "là gzip không hợp lệ: %{message}" - invalid_json: "là json không hợp lệ: %{message}" + invalid_gzip: "gzip không hợp lệ: %{message}" + invalid_json: "json không hợp lệ: %{message}" resources: schema: "Lược đồ" undisclosed: - parent: Undisclosed - The parent is invisible because of lacking permissions. - project: Undisclosed - The project is invisible because of lacking permissions. - ancestor: Không công khai - Tổ tiên không hiển thị vì thiếu quyền." - definingProject: Undisclosed - The project is invisible because of lacking permissions. + parent: Không được tiết lộ - Phụ huynh ẩn danh vì thiếu quyền. + project: Không được tiết lộ - Dự án vô hình vì thiếu quyền. + ancestor: Không được tiết lộ - Tổ tiên là vô hình vì thiếu quyền. + definingProject: Không được tiết lộ - Dự án vô hình vì thiếu quyền. doorkeeper: pre_authorization: status: "Xác thực" @@ -5087,9 +5085,9 @@ vi: #Common error messages invalid_request: unknown: "Yêu cầu thiếu tham số bắt buộc, bao gồm giá trị tham số không được hỗ trợ hoặc không đúng định dạng." - missing_param: "Thiếu tham số cần thiết: %{value}." - request_not_authorized: "Yêu cầu cần được xác thực. Tham số yêu cầu xác thực bị thiếu hoặc không hợp lệ." - invalid_redirect_uri: "Uri chuyển hướng được yêu cầu không đúng định dạng hoặc không khớp với URI chuyển hướng máy khách." + missing_param: "Thiếu tham số bắt buộc: %{value}." + request_not_authorized: "Yêu cầu cần phải được ủy quyền. Tham số bắt buộc để ủy quyền yêu cầu bị thiếu hoặc không hợp lệ." + invalid_redirect_uri: "Uri chuyển hướng được yêu cầu không đúng định dạng hoặc không khớp với URI chuyển hướng ứng dụng khách." unauthorized_client: "Máy khách không được phép thực hiện yêu cầu bằng phương pháp này." access_denied: "Chủ sở hữu tài nguyên hoặc máy chủ ủy quyền đã từ chối yêu cầu." invalid_scope: "Phạm vi yêu cầu không hợp lệ, không xác định hoặc không đúng định dạng." @@ -5097,12 +5095,12 @@ vi: server_error: "Máy chủ ủy quyền đã gặp phải một điều kiện không mong muốn khiến nó không thể đáp ứng yêu cầu." temporarily_unavailable: "Máy chủ ủy quyền hiện không thể xử lý yêu cầu do quá tải tạm thời hoặc bảo trì máy chủ." #Configuration error messages - credential_flow_not_configured: "Dòng thông tin chứng thực của Chủ sở hữu Tài nguyên không được cấu hình do Doorkeeper.configure.resource_owner_from_credentials chưa được cấu hình." - resource_owner_authenticator_not_configured: "Tìm chủ sở hữu tài nguyên không thành công do Doorkeeper.configure.resource_owner_authenticator chưa được cấu hình." + credential_flow_not_configured: "Luồng thông tin xác thực về mật khẩu của chủ sở hữu tài nguyên không thành công do Doorkeeper.configure.resource_owner_from_credentials chưa được định cấu hình." + resource_owner_authenticator_not_configured: "Tìm chủ sở hữu tài nguyên không thành công do Doorkeeper.configure.resource_owner_authenticator chưa được định cấu hình." admin_authenticator_not_configured: "Quyền truy cập vào bảng quản trị bị cấm do Doorkeeper.configure.admin_authenticator không được định cấu hình." #Access grant errors unsupported_response_type: "Máy chủ ủy quyền không hỗ trợ loại phản hồi này." - unsupported_response_mode: "Máy chủ xác thực không hỗ trợ chế độ phản hồi này." + unsupported_response_mode: "Máy chủ ủy quyền không hỗ trợ chế độ phản hồi này." #Access token errors invalid_client: "Xác thực ứng dụng khách không thành công do máy khách không xác định, không bao gồm xác thực ứng dụng khách hoặc phương thức xác thực không được hỗ trợ." invalid_grant: "Quyền được cung cấp không hợp lệ, hết hạn, bị thu hồi, không khớp với URI chuyển hướng được sử dụng trong yêu cầu ủy quyền hoặc được cấp cho một khách hàng khác." @@ -5114,7 +5112,7 @@ vi: revoke: unauthorized: "Bạn không được phép thu hồi mã thông báo này." forbidden_token: - missing_scope: 'Truy cập vào tài nguyên này yêu cầu phạm vi "%{oauth_scopes}".' + missing_scope: 'Việc truy cập vào tài nguyên này yêu cầu phạm vi "%{oauth_scopes}".' unsupported_browser: title: "Trình duyệt của bạn đã lỗi thời hoặc không được hỗ trợ." message: "Bạn có thể gặp phải lỗi và trải nghiệm không mong muốn trên trang này." @@ -5122,124 +5120,124 @@ vi: close_warning: "Bỏ qua cảnh báo này." oauth: application: - builtin: Built-in instance application - confidential: Bí mật + builtin: Ứng dụng phiên bản tích hợp + confidential: bí mật singular: "Ứng dụng OAuth" - scopes: "Phạm vi" - client_credentials: "Client credentials" + scopes: "phạm vi" + client_credentials: "Thông tin xác thực của khách hàng" plural: "Các ứng dụng OAuth" named: "Ứng dụng OAuth '%{name}'" new: "Ứng dụng OAuth mới" - non_confidential: Non confidential + non_confidential: Không bí mật default_scopes: "(Phạm vi mặc định)" instructions: - enabled: "Enable this application, allowing users to perform authorization grants with it." + enabled: "Kích hoạt ứng dụng này, cho phép người dùng thực hiện cấp phép với nó." name: "Tên ứng dụng của bạn. Điều này sẽ được hiển thị cho người dùng khác khi ủy quyền." redirect_uri_html: > Các URL người dùng được ủy quyền có thể được chuyển hướng đến. Mỗi mục nhập trên một dòng.
    Nếu bạn đang đăng ký một ứng dụng máy tính để bàn, hãy sử dụng URL sau. confidential: "Kiểm tra xem ứng dụng sẽ được sử dụng trong khi thông tin mật của khách hàng vẫn được đảm bảo. Các ứng dụng di động và Ứng dụng web được cho là không bảo mật." - scopes: "Chọn các phạm vi mà bạn muốn ứng dụng cấp quyền truy cập. Nếu không chọn phạm vi nào, mặc định là api_v3." + scopes: "Kiểm tra phạm vi bạn muốn ứng dụng cấp quyền truy cập. Nếu không có phạm vi nào được chọn, api_v3 sẽ được giả sử." client_credential_user_id: "ID người dùng tùy chọn để mạo danh khi khách hàng sử dụng ứng dụng này. Để trống để chỉ cho phép truy cập công cộng" - register_intro: "Nếu bạn đang phát triển ứng dụng khách API OAuth cho OpenProject, bạn có thể đăng ký nó bằng cách sử dụng biểu mẫu này để tất cả người dùng sử dụng." + register_intro: "Nếu bạn đang phát triển ứng dụng khách API OAuth cho OpenProject, bạn có thể đăng ký ứng dụng đó bằng biểu mẫu này để tất cả người dùng sử dụng." default_scopes: "" header: - builtin_applications: Built-in OAuth applications - other_applications: Other OAuth applications - empty_application_lists: No OAuth applications have been registered. + builtin_applications: Các ứng dụng OAuth tích hợp + other_applications: Các ứng dụng OAuth khác + empty_application_lists: Không có ứng dụng OAuth nào được đăng ký. client_id: "ID người dùng" client_secret_notice: > - Đây là lần duy nhất chúng tôi có thể in bí mật của khách hàng, vui lòng ghi lại và giữ an toàn. Nó nên được đối xử như mật khẩu và không thể được OpenProject lấy lại sau này. + Đây là lần duy nhất chúng tôi có thể in bí mật của khách hàng, vui lòng ghi lại và giữ an toàn. Nó phải được coi như một mật khẩu và OpenProject không thể lấy lại được sau này. authorization_dialog: authorize: "Xác thực" cancel: "Hủy bỏ và từ chối xác thực." - prompt_html: "Xác thực %{application_name} để sử dụng tài khoản của bạn %{login}?" - title: "Xác thực %{application_name}" + prompt_html: "Ủy quyền cho %{application_name} sử dụng tài khoản của bạn %{login}?" + title: "Ủy quyền %{application_name}" wants_to_access_html: > - Ứng dụng này yêu cầu quyền truy cập vào tài khoản OpenProject của bạn.
    Ứng dụng đã yêu cầu các quyền sau: + Ứng dụng này yêu cầu quyền truy cập vào tài khoản OpenProject của bạn.
    Nó đã yêu cầu các quyền sau: scopes: api_v3: "Truy cập đầy đủ API v3" - api_v3_text: "Ứng dụng sẽ nhận quyền đọc & viết đầy đủ vào API v3 của OpenProject để thực hiện các hành động thay mặt bạn." + api_v3_text: "Ứng dụng sẽ nhận được quyền truy cập đọc và ghi đầy đủ vào OpenProject API v3 để thay mặt bạn thực hiện các hành động." grants: created_date: "Đã duyệt" - scopes: "Phân Quyền" + scopes: "quyền" successful_application_revocation: "Thu hồi ứng dụng %{application_name} thành công." - none_given: "Chưa có ứng dụng OAuth nào được cấp quyền truy cập vào tài khoản người dùng của bạn." + none_given: "Không có ứng dụng OAuth nào được cấp quyền truy cập vào tài khoản người dùng của bạn." x_active_tokens: - other: "%{count} mã thông báo đang hoạt động" + other: "%{count} mã thông báo hoạt động" flows: - authorization_code: "Dòng mã xác thực" - client_credentials: "Dòng thông tin chứng thực khách hàng" - client_credentials: "Người dùng được sử dụng cho thông tin chứng thực khách hàng" - client_credentials_impersonation_set_to: "Người dùng thông tin chứng thực khách hàng được đặt thành" - client_credentials_impersonation_warning: "Lưu ý: Các khách hàng sử dụng dòng 'Thông tin chứng thực khách hàng' trong ứng dụng này sẽ có quyền của người dùng này." + authorization_code: "Luồng mã ủy quyền" + client_credentials: "Luồng thông tin xác thực của khách hàng" + client_credentials: "Người dùng được sử dụng cho thông tin xác thực của Khách hàng" + client_credentials_impersonation_set_to: "Người dùng thông tin đăng nhập của khách hàng được đặt thành" + client_credentials_impersonation_warning: "Lưu ý: Khách hàng sử dụng luồng 'Thông tin xác thực khách hàng' trong ứng dụng này sẽ có các quyền của người dùng này" client_credentials_impersonation_html: > - Theo mặc định, OpenProject cung cấp xác thực OAuth 2.0 qua %{authorization_code_flow_link}. Bạn có thể chọn kích hoạt %{client_credentials_flow_link}, nhưng bạn phải cung cấp một người dùng mà yêu cầu sẽ được thực hiện thay mặt cho họ. - authorization_error: "Đã xảy ra lỗi xác thực." - revoke_my_application_confirmation: "Bạn thực sự muốn xóa ứng dụng này không? Điều này sẽ thu hồi %{token_count} mã thông báo còn hoạt động cho nó." - my_registered_applications: "Các ứng dụng OAuth đã đăng ký" + Theo mặc định, OpenProject cung cấp ủy quyền OAuth 2.0 thông qua %{authorization_code_flow_link}. Bạn có thể tùy ý bật %{client_credentials_flow_link}, nhưng bạn phải cung cấp người dùng mà yêu cầu của họ sẽ được thực hiện thay mặt họ. + authorization_error: "Đã xảy ra lỗi ủy quyền." + revoke_my_application_confirmation: "Bạn có thực sự muốn xóa ứng dụng này? Điều này sẽ thu hồi %{token_count} hoạt động cho nó." + my_registered_applications: "Ứng dụng OAuth đã đăng ký" oauth_client: urn_connection_status: - connected: "Đã kết nối" - error: "Lỗi" - failed_authorization: "Xác thực không thành công" - not_connected: "Not connected" + connected: "đã kết nối" + error: "lỗi" + failed_authorization: "Ủy quyền không thành công" + not_connected: "Không được kết nối" labels: label_oauth_integration: "Tích hợp OAuth2" label_redirect_uri: "URI chuyển hướng" label_request_token: "Yêu cầu mã thông báo" label_refresh_token: "Làm mới mã thông báo" errors: - oauth_authorization_code_grant_had_errors: "Cấp quyền xác thực OAuth2 không thành công" - oauth_reported: "Nhà cung cấp OAuth2 đã báo cáo" - oauth_returned_error: "OAuth2 đã trả về lỗi" - oauth_returned_json_error: "OAuth2 đã trả về lỗi JSON" - oauth_returned_http_error: "OAuth2 đã trả về lỗi mạng" - oauth_returned_standard_error: "OAuth2 đã trả về lỗi nội bộ" - wrong_token_type_returned: "OAuth2 đã trả về loại mã thông báo sai, mong đợi AccessToken::Bearer" - oauth_issue_contact_admin: "OAuth2 đã báo cáo lỗi. Vui lòng liên hệ với quản trị viên hệ thống của bạn." - oauth_client_not_found: "Khách hàng OAuth2 không tìm thấy ở điểm cuối 'callback' (redirect_uri)." + oauth_authorization_code_grant_had_errors: "Cấp quyền OAuth2 không thành công" + oauth_reported: "Đã báo cáo nhà cung cấp OAuth2" + oauth_returned_error: "OAuth2 trả về lỗi" + oauth_returned_json_error: "OAuth2 trả về lỗi JSON" + oauth_returned_http_error: "OAuth2 trả về lỗi mạng" + oauth_returned_standard_error: "OAuth2 trả về lỗi nội bộ" + wrong_token_type_returned: "OAuth2 trả về sai loại mã thông báo, mong đợi AccessToken::Bearer" + oauth_issue_contact_admin: "OAuth2 đã báo lỗi. Vui lòng liên hệ với quản trị viên hệ thống của bạn." + oauth_client_not_found: "Không tìm thấy ứng dụng khách OAuth2 trong điểm cuối 'gọi lại' (redirect_uri)." refresh_token_called_without_existing_token: > - Lỗi nội bộ: Đã gọi refresh_token mà không có mã thông báo tồn tại trước đó. - refresh_token_updated_failed: "Lỗi trong việc cập nhật OAuthClientToken" + Lỗi nội bộ: Được gọi là Refresh_token mà không có mã thông báo hiện có trước đó. + refresh_token_updated_failed: "Lỗi trong quá trình cập nhật OAuthClientToken" oauth_client_not_found_explanation: > - Lỗi này xuất hiện sau khi bạn đã cập nhật client_id và client_secret trong OpenProject, nhưng chưa cập nhật trường 'Return URI' trong nhà cung cấp OAuth2. - oauth_code_not_present: "OAuth2 'code' không tìm thấy ở điểm cuối 'callback' (redirect_uri)." + Lỗi này xuất hiện sau khi bạn đã cập nhật client_id và client_secret trong OpenProject nhưng chưa cập nhật trường 'Return URI' trong nhà cung cấp OAuth2. + oauth_code_not_present: "Không tìm thấy 'mã' OAuth2 trong điểm cuối 'gọi lại' (redirect_uri)." oauth_code_not_present_explanation: > - Lỗi này xuất hiện nếu bạn đã chọn loại response_type sai trong nhà cung cấp OAuth2. Response_type nên là 'code' hoặc tương tự. - oauth_state_not_present: "OAuth2 'state' không tìm thấy ở điểm cuối 'callback' (redirect_uri)." + Lỗi này xuất hiện nếu bạn chọn sai loại phản hồi trong nhà cung cấp OAuth2. Response_type phải là 'code' hoặc tương tự. + oauth_state_not_present: "Không tìm thấy 'trạng thái' OAuth2 trong điểm cuối 'gọi lại' (redirect_uri)." oauth_state_not_present_explanation: > - 'State' được sử dụng để chỉ OpenProject nơi tiếp tục sau khi xác thực OAuth2 thành công. Thiếu 'state' là lỗi nội bộ có thể xuất hiện trong quá trình thiết lập. Vui lòng liên hệ với quản trị viên hệ thống của bạn. + 'Trạng thái' được sử dụng để chỉ ra cho OpenProject nơi tiếp tục sau khi ủy quyền OAuth2 thành công. 'Trạng thái' bị thiếu là lỗi nội bộ có thể xuất hiện trong quá trình thiết lập. Vui lòng liên hệ với quản trị viên hệ thống của bạn. rack_oauth2: - client_secret_invalid: "Bí mật khách hàng không hợp lệ (client_secret_invalid)" + client_secret_invalid: "Bí mật của ứng dụng khách không hợp lệ (client_secret_invalid)" invalid_request: > - Máy chủ Xác thực OAuth2 đã phản hồi với 'invalid_request'. Lỗi này xuất hiện nếu bạn cố gắng xác thực nhiều lần hoặc trong trường hợp có sự cố kỹ thuật. - invalid_response: "Máy chủ Xác thực OAuth2 đã cung cấp phản hồi không hợp lệ (invalid_response)" - invalid_grant: "Máy chủ Xác thực OAuth2 yêu cầu bạn xác thực lại (invalid_grant)." - invalid_client: "Máy chủ Xác thực OAuth2 không nhận ra OpenProject (invalid_client)." - unauthorized_client: "Máy chủ Xác thực OAuth2 từ chối loại cấp quyền (unauthorized_client)" - unsupported_grant_type: "Máy chủ Xác thực OAuth2 yêu cầu bạn xác thực lại (unsupported_grant_type)." - invalid_scope: "Bạn không được phép truy cập tài nguyên yêu cầu (invalid_scope)." + Máy chủ ủy quyền OAuth2 phản hồi bằng 'invalid_request'. Lỗi này xuất hiện nếu bạn cố gắng ủy quyền nhiều lần hoặc trong trường hợp có vấn đề về kỹ thuật. + invalid_response: "Máy chủ ủy quyền OAuth2 đã cung cấp phản hồi không hợp lệ (invalid_response)" + invalid_grant: "Máy chủ ủy quyền OAuth2 yêu cầu bạn ủy quyền lại (invalid_grant)." + invalid_client: "Máy chủ ủy quyền OAuth2 không nhận ra OpenProject (invalid_client)." + unauthorized_client: "Máy chủ ủy quyền OAuth2 từ chối loại cấp phép (unauthorized_client)" + unsupported_grant_type: "Máy chủ ủy quyền OAuth2 yêu cầu bạn ủy quyền lại (unsupported_grant_type)." + invalid_scope: "Bạn không được phép truy cập tài nguyên được yêu cầu (invalid_scope)." http: request: - failed_authorization: "Yêu cầu phía máy chủ không được xác thực thành công." - missing_authorization: "Yêu cầu phía máy chủ không thành công do thiếu thông tin xác thực." + failed_authorization: "Yêu cầu phía máy chủ không tự ủy quyền được." + missing_authorization: "Yêu cầu phía máy chủ không thành công do thiếu thông tin ủy quyền." response: - unexpected: "Nhận được phản hồi không mong đợi." + unexpected: "Đã nhận được phản hồi bất ngờ." you: bạn link: liên kết plugin_openproject_auth_plugins: - name: "Các plugin xác thực OpenProject" + name: "Plugin xác thực OpenProject" description: "Tích hợp các nhà cung cấp chiến lược OmniAuth để xác thực trong OpenProject." plugin_openproject_auth_saml: name: "OmniAuth SAML / Đăng nhập một lần" - description: "Thêm nhà cung cấp OmniAuth SAML vào OpenProject" + description: "Thêm nhà cung cấp SAML OmniAuth vào OpenProject" enterprise_plans: - legacy_enterprise: "Enterprise Plan" + legacy_enterprise: "Kế hoạch doanh nghiệp" token: hashed_token: display_value_placeholder: "***" external_link_warning: - title: "Leaving OpenProject" - warning_message: "You are about to leave OpenProject and visit an external website. Please be aware that external websites are not under our control and may have different privacy and security policies." - continue_message: "Are you sure you want to proceed to the following external link?" - continue_button: "Continue to external website" + title: "Rời khỏi OpenProject" + warning_message: "Bạn sắp rời khỏi OpenProject và truy cập vào một trang web bên ngoài. Xin lưu ý rằng các trang web bên ngoài không nằm dưới sự kiểm soát của chúng tôi và có thể có các chính sách bảo mật và an ninh khác nhau." + continue_message: "Bạn có chắc chắn muốn tiếp tục đến liên kết bên ngoài sau đây không?" + continue_button: "Tiếp tục đến trang web bên ngoài" diff --git a/config/locales/crowdin/zh-CN.yml b/config/locales/crowdin/zh-CN.yml index 3f7e879d573..1fe90959025 100644 --- a/config/locales/crowdin/zh-CN.yml +++ b/config/locales/crowdin/zh-CN.yml @@ -111,21 +111,21 @@ zh-CN: link: "Webhook" mcp_configurations: index: - description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." - resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + description: "Model Context Protocol 允许 AI 智能体向其用户提供此 OpenProject 实例所公开的工具和资源。" + resources_heading: "资源" + resources_description: "OpenProject 实现了以下工具。每种工具都可以根据需要启用、重命名和描述。有关详情,请参阅[关于 MCP 资源的文档](docs_url)。" resources_submit: "更新资源" - tools_heading: "Tools" - tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." + tools_heading: "工具" + tools_description: "OpenProject 实现了以下工具。每种工具都可以根据需要启用、重命名和描述。有关详情,请参阅[关于 MCP 工具的文档](docs_url)。" tools_submit: "更新工具" multi_update: - success: "MCP configurations were updated successfully." + success: "MCP 配置已成功更新。" server_form: - description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + description_caption: "如何向连接到 MCP 服务器的其他应用程序描述该 MCP 服务器。" + title_caption: "展示给连接到 MCP 服务器的应用程序的简短标题。" update: - failure: "MCP configuration could not be updated." - success: "MCP configuration was updated successfully." + failure: "MCP 配置无法更新。" + success: "MCP 配置已成功更新。" scim_clients: authentication_methods: sso: "来自身份提供商的 JWT" @@ -720,11 +720,11 @@ zh-CN: create_button: "创建" name_label: "令牌名称" created_dialog: - one_time_warning: "This is the only time you will see this token. Make sure to copy it now." + one_time_warning: "这将是您看到此令牌的唯一机会。请务必立即复制。" token/api: title: "API 令牌已生成" token/rss: - title: "The RSS token has been generated" + title: "RSS 令牌已生成" failed_to_reset_token: "无法重置访问令牌: %{error}" failed_to_create_token: "创建访问令牌失败: %{error}" failed_to_revoke_token: "撤销访问令牌失败: %{error}" @@ -1241,9 +1241,9 @@ zh-CN: port: "端口" tls_certificate_string: "LDAP 服务器 SSL 证书" mcp_configuration: - enabled: Enabled - title: Title - description: Description + enabled: 已启用 + title: 标题 + description: 描述 member: roles: "角色" notification: @@ -1502,7 +1502,7 @@ zh-CN: even: "必须是偶数。" exclusion: "是保留关键字。" feature_disabled: 不可用。 - feature_disabled_for_project: is disabled for this project. + feature_disabled_for_project: 已对此项目禁用。 file_too_large: "太大 (最大大小为 %{count} 字节)。" filter_does_not_exist: "筛选器不存在。" format: "与预期的格式“%{expected}”不匹配。" @@ -1922,7 +1922,7 @@ zh-CN: token/api: other: 访问令牌 token/rss: - other: "RSS tokens" + other: "RSS 令牌" type: other: "类型" user: "用户" @@ -2402,7 +2402,7 @@ zh-CN: baseline_comparison: 基线比较 board_view: 高级面板 calculated_values: 计算值 - capture_external_links: Capture External Links + capture_external_links: 捕获外部链接 internal_comments: 内部评论 custom_actions: 自定义操作 custom_field_hierarchies: 层级 @@ -2412,12 +2412,12 @@ zh-CN: edit_attribute_groups: 编辑属性组 gantt_pdf_export: 甘特图 PDF 导出 ldap_groups: LDAP 用户和群组同步 - mcp_server: MCP Server + mcp_server: MCP 服务器 nextcloud_sso: Nextcloud存储的单点登录(SSO) one_drive_sharepoint_file_storage: OneDrive/SharePoint 文件存储 placeholder_users: 占位符用户 portfolio_management: 项目组合管理 - project_creation_wizard: Project initiation request + project_creation_wizard: 项目启动请求 project_list_sharing: 项目列表共享 readonly_work_packages: 只读工作包 scim_api: SCIM 服务器 API @@ -2437,7 +2437,7 @@ zh-CN: plan_text_html: "从 %{plan_name} 开始可用。" unlimited: "无限制" already_have_token: > - 已经有令牌?使用下方按钮添加以升级到预订的 Enterprise 方案。 + 已经有令牌?使用下方按钮添加以升级到预订的企业版方案。 hide_banner: "隐藏此广告" homescreen_description: > 企业计划通过额外的[企业附加组件](enterprise_url) 和专业支持扩展了OpenProject社区版,是在关键任务环境中运行OpenProject的组织的理想选择。 @@ -2459,7 +2459,7 @@ zh-CN: customize_life_cycle: description: "创建和组织与PM2项目周期规划不同的项目阶段。" capture_external_links: - description: "Prevent social engineering attacks by capturing and warning about external links before users visit them." + description: "在用户访问外部链接前捕获链接并发出警告,以防范社会工程攻击。" work_package_query_relation_columns: description: "需要在工作包列表中看到关系或子项吗?" edit_attribute_groups: @@ -2490,7 +2490,7 @@ zh-CN: title: "自定义操作" description: "自定义操作是一键快捷方式,指向一组预定义的操作,您可以根据状态、角色、类型或项目在某些工作包上使用这些操作。" mcp_server: - description: "Integrate AI agents with your OpenProject instance through MCP." + description: "通过 MCP 将 AI 智能体与 OpenProject 实例集成。" nextcloud_sso: title: "Nextcloud存储的单点登录(SSO)" description: "使用单点登录为您的 Nextcloud 存储启用无缝且安全的身份验证。简化访问管理并提升用户便利性。" @@ -2503,7 +2503,7 @@ zh-CN: virus_scanning: description: "确保在 OpenProject 中上传的文件在被其他用户访问之前进行病毒扫描。" project_creation_wizard: - description: "Generate a step-by-step wizard to help project managers fill out a project initiation request." + description: "生成分步向导,帮助项目经理填写项目启动请求。" placeholder_users: title: 占位符用户 description: > @@ -2574,7 +2574,7 @@ zh-CN: error_color_could_not_be_saved: "无法保存颜色" error_cookie_missing: "OpenProject cookie 丢失。请确保已启用 cookie,因为如果不启用,此应用程序将无法正常运行。" error_custom_option_not_found: "选项不存在。" - error_enterprise_plan_needed: "您需要 %{plan} Enterprise 方案才能执行此操作。" + error_enterprise_plan_needed: "您需要 %{plan}企业版方案才能执行此操作。" error_enterprise_activation_user_limit: "无法激活您的帐户 (已达到用户限制)。请与管理员联系以获取访问权限。" error_enterprise_token_invalid_domain: "企业版未激活。您的企业版令牌的域名(%{actual})与系统的主机名(%{expected})不匹配。" error_failed_to_delete_entry: "无法删除此条目。" @@ -2802,15 +2802,15 @@ zh-CN: #We need to include the version to invalidate outdated translations in other locales "17_1": new_features_title: > - The release contains various new features and improvements, such as: + 此版本包含多项新功能和改进,例如: new_features_list: - line_0: Automated project initiation (Enterprise add-on). - line_1: "Meetings: add new or existing work packages as outcomes." - line_2: "Meetings: show participant responses in iCal subscriptions." - line_3: "Recurring meetings: duplicate agenda items to the next occurrence." - line_4: "Release to Community: Attribute highlighting." - line_5: Warning before opening external links in user-provided content (Enterprise add-on). - line_6: Improved performance and user experience, including the Activity tab and Documents module. + line_0: 自动化项目启动(企业版附加组件)。 + line_1: "会议:添加新工作包或现有工作包作为成果。" + line_2: "会议:在 iCal 订阅中显示参加者的回复。" + line_3: "定期会议:将议程条目复制到下一事件。" + line_4: "发布到社区:特性高亮显示。" + line_5: 在打开用户提供内容中的外部链接前发出警告(企业版附加组件)。 + line_6: 提升了性能和用户体验,包括“活动”选项卡和“文档”模块。 links: upgrade_enterprise_edition: "升级到企业版" postgres_migration: "将您的安装迁移到 PostgreSQL" @@ -2878,7 +2878,7 @@ zh-CN: instructions_after_error: "你可以尝试点击 %{signin} 重新登录。如果错误仍然存在,请向您的管理员寻求帮助。" menus: admin: - ai: "Artificial Intelligence (AI)" + ai: "人工智能 (AI)" aggregation: "聚合" api_and_webhooks: "API 和 Webhook" mail_notification: "电子邮件通知" @@ -2888,7 +2888,7 @@ zh-CN: label: "添加…" my_account: access_tokens: - description: "Provider tokens are issued by OpenProject, allowing other applications to access it. Client tokens are issued by other applications, allowing OpenProject to access them." + description: "提供商令牌由 OpenProject 签发,允许其他应用程序访问。客户端令牌由其他应用程序签发,允许 OpenProject 对其进行访问。" no_results: title: "没有要显示的访问令牌" description: "他们都已被禁用。他们可以在管理菜单重新启用。" @@ -2900,48 +2900,48 @@ zh-CN: simple_revoke_confirmation: "确定要撤销此令牌吗?" tabs: client: - title: "Client tokens" + title: "客户端令牌" provider: - title: "Provider tokens" + title: "提供商令牌" token/api: - blank_description: "There is no API token yet. You can create one using the button below." - blank_title: "No API token" + blank_description: "目前还没有 API 令牌。您可以使用下方按钮创建一个。" + blank_title: "没有 API 令牌" title: "API" - table_title: "API tokens" + table_title: "API 令牌" text_hint: "API 令牌允许第三方应用程序通过 REST API 与此 OpenProject 实例通信。" - static_token_name: "API token" + static_token_name: "API 令牌" disabled_text: "管理员未启用 API 令牌。请联系管理员以使用此功能。" - add_button: "API token" + add_button: "API 令牌" ical: - blank_description: "To add an iCalendar token, subscribe to a new or existing calendar from within the Calendar module of a project. You must have the necessary permissions." - blank_title: "No iCalendar token" + blank_description: "要添加 iCalendar 令牌,请从项目的“日历”模块中订阅新日历或现有日历。您必须拥有必要的权限。" + blank_title: "没有 iCalendar 令牌" title: "iCalendar" - table_title: "iCalendar tokens" + table_title: "iCalendar 令牌" text_hint_link: "iCalendar 令牌允许用户[订阅OpenProject日历](docs_url),并查看来自外部客户端的最新工作包信息。" disabled_text: "管理员未启用 iCalendar 订阅。请联系管理员以使用此功能。" oauth_application: - active_tokens: "Active tokens" - blank_description: "There is no third-party application access configured and active for you." - blank_title: "No OAuth application token" - last_used_at: "Last used at" + active_tokens: "有效令牌" + blank_description: "没有为您配置和激活第三方应用程序访问权限。" + blank_title: "没有 OAuth 应用程序令牌" + last_used_at: "最近使用时间" title: "OAuth" - table_title: "OAuth application tokens" - text_hint: "OAuth application tokens allow third-party applications to connect with this OpenProject instance." + table_title: "OAuth 应用程序令牌" + text_hint: "OAuth 应用程序令牌允许第三方应用程序与此 OpenProject 实例关联。" oauth_client: - blank_description: "There are no OAuth client tokens yet." - blank_title: "无 OAuth 客户端令牌" + blank_description: "目前还没有 OAuth 客户端令牌。" + blank_title: "没有 OAuth 客户端令牌" failed: "出错了,无法移除令牌。请稍后再试。" integration_type: "集成类型" table_title: "OAuth 客户端令牌" - text_hint: "OAuth 客户端令牌允许此 OpenProject 实例与文件存储等外部应用程序进行连接。" + text_hint: "OAuth 客户端令牌允许此 OpenProject 实例与文件存储等外部应用程序关联。" title: "OAuth" remove_token: "是否确实要移除此令牌?您需要再次登录 %{integration}。" removed: "OAuth 客户端令牌已成功移除" unknown_integration: "未知" token/rss: - add_button: "RSS token" - blank_description: "目前还没有 RSS 标记。您可以使用下面的按钮创建一个。" - blank_title: "无 RSS 令牌" + add_button: "RSS 令牌" + blank_description: "目前还没有 RSS 令牌。您可以使用下方按钮创建一个。" + blank_title: "没有 RSS 令牌" title: "RSS" table_title: "RSS 令牌" text_hint: "RSS 令牌允许用户通过外部 RSS 阅读器了解此 OpenProject 实例中的最新变化。" @@ -4253,7 +4253,7 @@ zh-CN: 允许这些协议在工作包描述、长文本字段和评论中显示为链接。例如,%{tel_code} 或 %{element_code}。每行输入一个协议。
    协议 %{http_code}、%{https_code} 和 %{mailto_code} 始终允许。 setting_capture_external_links: "捕获外部链接" setting_capture_external_links_text: > - When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + 启用后,格式化文本中的所有外部链接在离开应用程序前都会重定向至警告页面。这有助于保护用户免受潜在恶意外部网站的危害。 setting_after_first_login_redirect_url: "首次登录重定向" setting_after_first_login_redirect_url_text_html: > 设置用户首次登录后的重定向路径。如果该路径为空,则重定向到主页以进行导览介绍。
    示例: /my/page @@ -4267,16 +4267,16 @@ zh-CN: 如果启用了 CORS ,这些是允许访问 OpenProject API 的源。
    请查看有关“源”标题的文档,了解如何指定预期值。 setting_apiv3_write_readonly_attributes: "对只读属性的写访问权限" setting_apiv3_write_readonly_attributes_instructions: > - If enabled, the API will allow administrators to write static read-only attributes during creation, such as createdAt and author. + 启用后,API 将允许管理员在创建过程中写入静态只读属性,如 createdAt 和 author。 setting_apiv3_write_readonly_attributes_warning: > - This setting has a use-case for e.g., importing data, but allows administrators to impersonate the creation of items as other users. All creation requests are being logged however with the true author. + 此设置可用于数据导入等用例,但允许管理员以其他用户身份模拟项目创建。不过,所有创建请求都会记录真实作者信息。 setting_apiv3_write_readonly_attributes_additional: > - For more information on attributes and supported resources, please see the %{api_documentation_link}. + 有关特性和受支持资源的详细信息,请参阅%{api_documentation_link}。 setting_apiv3_max_page_size: "最大 API 页面大小" setting_apiv3_max_page_size_instructions: > - Set the maximum page size the API will respond with. It will not be possible to perform API requests that return more values on a single page. + 设置 API 将响应的最大页面大小。将无法执行在单个页面上返回更多值的 API 请求。 setting_apiv3_max_page_size_warning: > - Please only change this value if you are sure why you need it. Setting to a high value will result in significant performance impacts, while a value lower than the per page options will cause errors in paginated views. + 请仅在确定有必要的情况下更改该值。如果设置的值过高,会严重影响性能,而如果设置的值低于每页选项,则会导致分页视图出错。 setting_apiv3_docs: "文档" setting_apiv3_docs_enabled: "启用文档页面" setting_apiv3_docs_enabled_instructions_html: > @@ -4461,7 +4461,7 @@ zh-CN: omniauth_direct_login_hint_html: > 如果激活该选项,登录请求将重定向到已配置的 omniauth 提供商。登录下拉菜单和登录页面将被禁用。
    注意:除非同时禁用密码登录,否则启用此选项后,用户仍可通过访问 %{internal_path}登录页面。 remapping_existing_users_hint: > - If enabled, allows any configured identity provider to login existing users based on their username, even if the user never signed in through that provider before. This can be useful when migrating the OpenProject instance to a new SSO provider, but is not recommended when using a provider that is not trusted by all users of your instance. + 如果启用,则允许任何已配置的身份提供商基于用户名登录现有用户,即使用户此前从未通过该提供商登录过。在将 OpenProject 实例迁移到新的 SSO 提供商时,此功能会有一定的帮助,但如果使用的提供商并未受到您的实例所有用户的信任,则不建议启用。 attachments: whitelist_text_html: > 为上传的文件定义有效文件扩展名和/或 MIME 类型列表。
    输入文件扩展名(例如 %{ext_example})或 MIME 类型(例如 %{mime_example})。
    留空以允许上传任何文件类型。允许输入多个值(每个值一行)。 @@ -4567,7 +4567,7 @@ zh-CN: project_mandate: "项目授权" submission: description_template: > - **This work package was automatically created upon completion of the %{wizard_name} workflow.** A PDF artifact containing all submitted information has been generated and attached to this work package for reference and audit purposes. If you need to update or re-run the initiation steps, you can reopen the wizard at any time by using the link below: + **此工作包是在 %{wizard_name} 工作流完成后自动创建的**。 包含所有已提交信息的 PDF 工件已生成并附加到此工作包,以供参考和审核。 如果需要更新或重新运行启动步骤,您可以随时使用下方链接重新打开向导: description: "当用户提交项目启动请求时,将创建一个新的工作包,并以 PDF 文件形式附上请求工件。以下设置定义了新工作包的类型、状态和受理人。" work_package_type: "工作包类型" work_package_type_caption: "应当用于存储已完成工件的工作包类型。" @@ -4825,8 +4825,8 @@ zh-CN: other: "暂时锁定(%{count} 次尝试登录失败)" confirm_status_change: "您即将更改 \"%{name}\" 的状态。确实要继续吗?" deleted: "已删除的用户" - error_status_change_self: "You cannot change your own user status." - error_admin_change_on_non_admin: "Only administrators can change the status of administrator users." + error_status_change_self: "您不能更改自己的用户状态。" + error_admin_change_on_non_admin: "只有管理员可以更改管理员用户的状态。" error_status_change_failed: "用户状态更改失败,因为以下错误: %{errors}" invite: 通过电子邮件邀请用户 invited: 已邀请 @@ -5230,7 +5230,7 @@ zh-CN: hashed_token: display_value_placeholder: "***" external_link_warning: - title: "Leaving OpenProject" - warning_message: "You are about to leave OpenProject and visit an external website. Please be aware that external websites are not under our control and may have different privacy and security policies." - continue_message: "Are you sure you want to proceed to the following external link?" - continue_button: "Continue to external website" + title: "离开 OpenProject" + warning_message: "您即将离开 OpenProject 并访问外部网站。请注意,外部网站不受我们的管控,可能采用不同的隐私和安全政策。" + continue_message: "确定要继续访问以下外部链接吗?" + continue_button: "继续访问外部网站" diff --git a/modules/auth_saml/config/locales/crowdin/tr.yml b/modules/auth_saml/config/locales/crowdin/tr.yml index 401e865a415..4e39d034a95 100644 --- a/modules/auth_saml/config/locales/crowdin/tr.yml +++ b/modules/auth_saml/config/locales/crowdin/tr.yml @@ -29,7 +29,7 @@ tr: invalid_certificate: "%{additional_message}, Geçerli bir PEM formatlı sertifika değildir." invalid_private_key: "%{additional_message}, Geçerli bir PEM formatında özel anahtar değildir" certificate_expired: "Süresi dolmuştur ve artık kullanılamaz." - unmatched_private_key: "verilen sertifikaya ait değil" + unmatched_private_key: "sertifikaya ait değil" saml: menu_title: SAML sağlayıcıları delete_title: SAML sağlayıcısını sil @@ -49,30 +49,30 @@ tr: label_metadata_endpoint: OpenProject Metaveri bitiş noktası label_openproject_information: OpenProject Bilgileri label_configuration_details: "Kimlik sağlayıcı uç noktaları ve sertifikaları" - label_configuration_encryption: "Signatures and Encryption" - label_add_new: New SAML identity provider + label_configuration_encryption: "İmzalar ve Şifreleme" + label_add_new: Yeni SAML kimlik sağlayıcısı label_edit: SAML kimlik sağlayıcısını düzenle %{name} label_uid: Dahili kullanıcı kimliği label_mapping: İlişkilendirme label_requested_attribute_for: "%{attribute} için nitelik talebi" - no_results_table: No SAML identity providers have been defined yet. + no_results_table: Henüz hiçbir SAML kimlik sağlayıcısı tanımlanmamış. notice_created: A new SAML identity provider was successfully created. plural: SAML kimlik sağlayıcıları singular: SAML kimlik sağlayıcısı requested_attributes: Talep edilen nitelikler attribute_mapping: Öznitelik eşlemesi attribute_mapping_text: > - The following fields control which attributes provided by the SAML identity provider are used to provide user attributes in OpenProject + Aşağıdaki alanlar SAML kimlik sağlayıcısı tarafından sağlanan hangi özniteliklerin OpenProject'te kullanıcı öznitelikleri sağlamak için kullanılacağını kontrol eder metadata: - dialog: "This is the URL where the OpenProject SAML metadata is available. Optionally use it to configure your identity provider:" + dialog: "Bu, OpenProject SAML meta verilerinin mevcut olduğu URL'dir. İsteğe bağlı olarak kimlik sağlayıcınızı yapılandırmak için kullanın:" upsell: - title: "Single Sign-On (SSO) with SAML" + title: "SAML ile Çoklu Oturum Açma (SSO)" description: OpenProject'i bir SAML kimlik sağlayıcısına bağlama request_attributes: title: 'Talep edilen nitelikler' legend: > These attributes are added to the SAML XML metadata to signify to the identify provider which attributes OpenProject requires. You may still need to explicitly configure your identity provider to send these attributes. Please refer to your IdP's documentation. - name: 'Requested attribute key' + name: 'İstenen öznitelik anahtarı' format: 'Öznitelik biçimi' section_headers: configuration: "Birincil yapılandırma" diff --git a/modules/auth_saml/config/locales/crowdin/vi.yml b/modules/auth_saml/config/locales/crowdin/vi.yml index 0ce4b7903a3..d3832a3dd4c 100644 --- a/modules/auth_saml/config/locales/crowdin/vi.yml +++ b/modules/auth_saml/config/locales/crowdin/vi.yml @@ -2,155 +2,155 @@ vi: activerecord: attributes: saml/provider: - display_name: Tên - identifier: Định danh - secret: Bí mật - scope: Phạm vi - assertion_consumer_service_url: ACS (Assertion consumer service) URL - limit_self_registration: Giới hạn đăng ký tự động - sp_entity_id: Service entity ID - metadata_url: Identity provider metadata URL - name_identifier_format: Name identifier format - idp_sso_service_url: Identity provider login endpoint - idp_slo_service_url: Identity provider logout endpoint - idp_cert: Public certificate of identity provider - authn_requests_signed: Sign SAML AuthnRequests - want_assertions_signed: Require signed responses - want_assertions_encrypted: Require encrypted responses - certificate: Certificate used by OpenProject for SAML requests - private_key: Corresponding private key for OpenProject SAML requests - signature_method: Signature algorithm - digest_method: Digest algorithm - format: "Định dạng" + display_name: tên + identifier: định danh + secret: bí mật + scope: phạm vi + assertion_consumer_service_url: URL ACS (Xác nhận dịch vụ tiêu dùng) + limit_self_registration: Hạn chế tự đăng ký + sp_entity_id: ID thực thể dịch vụ + metadata_url: URL siêu dữ liệu của nhà cung cấp danh tính + name_identifier_format: Định dạng nhận dạng tên + idp_sso_service_url: Điểm cuối đăng nhập của nhà cung cấp danh tính + idp_slo_service_url: Điểm cuối đăng xuất của nhà cung cấp danh tính + idp_cert: Giấy chứng nhận công khai của nhà cung cấp danh tính + authn_requests_signed: Ký yêu cầu xác thực SAML + want_assertions_signed: Yêu cầu phản hồi có chữ ký + want_assertions_encrypted: Yêu cầu phản hồi được mã hóa + certificate: Chứng chỉ được OpenProject sử dụng cho các yêu cầu SAML + private_key: Khóa riêng tương ứng cho các yêu cầu SAML của OpenProject + signature_method: Thuật toán chữ ký + digest_method: Thuật toán tóm tắt + format: "định dạng" icon: "Biểu tượng tùy chỉnh" errors: models: saml/provider: - invalid_certificate: "is not a valid PEM-formatted certificate: %{additional_message}" - invalid_private_key: "is not a valid PEM-formatted private key: %{additional_message}" - certificate_expired: "is expired and can no longer be used." - unmatched_private_key: "does not belong to the given certificate" + invalid_certificate: "không phải là chứng chỉ có định dạng PEM hợp lệ: %{additional_message}" + invalid_private_key: "không phải là khóa riêng có định dạng PEM hợp lệ: %{additional_message}" + certificate_expired: "đã hết hạn và không thể sử dụng được nữa." + unmatched_private_key: "không thuộc về chứng chỉ đã cho" saml: - menu_title: SAML providers - delete_title: Delete SAML provider + menu_title: Nhà cung cấp SAML + delete_title: Xóa nhà cung cấp SAML info: - title: "SAML Protocol Configuration Parameters" + title: "Thông số cấu hình giao thức SAML" description: > - Use these parameters to configure your identity provider connection to OpenProject. + Sử dụng các tham số này để định cấu hình kết nối nhà cung cấp danh tính của bạn với OpenProject. metadata_parser: - success: "Successfully updated the configuration using the identity provider metadata." - invalid_url: "Provided metadata URL is invalid. Provide a HTTP(s) URL." - error: "Failed to retrieve the identity provider metadata: %{error}" + success: "Đã cập nhật thành công cấu hình bằng siêu dữ liệu của nhà cung cấp danh tính." + invalid_url: "URL siêu dữ liệu được cung cấp không hợp lệ. Cung cấp (các) URL HTTP." + error: "Không truy xuất được siêu dữ liệu của nhà cung cấp danh tính: %{error}" providers: - label_empty_title: "No SAML providers configured yet." - label_empty_description: "Add a provider to see them here." - label_automatic_configuration: Automatic configuration - label_metadata: Metadata - label_metadata_endpoint: OpenProject metadata endpoint - label_openproject_information: OpenProject information - label_configuration_details: "Identity provider endpoints and certificates" - label_configuration_encryption: "Signatures and Encryption" - label_add_new: New SAML identity provider - label_edit: Edit SAML identity provider %{name} - label_uid: Internal user id - label_mapping: Mapping - label_requested_attribute_for: "Requested attribute for: %{attribute}" - no_results_table: No SAML identity providers have been defined yet. - notice_created: A new SAML identity provider was successfully created. - plural: SAML identity providers - singular: SAML identity provider - requested_attributes: Requested attributes + label_empty_title: "Chưa có nhà cung cấp SAML nào được định cấu hình." + label_empty_description: "Thêm nhà cung cấp để xem chúng ở đây." + label_automatic_configuration: Cấu hình tự động + label_metadata: Siêu dữ liệu + label_metadata_endpoint: Điểm cuối siêu dữ liệu OpenProject + label_openproject_information: Thông tin dự án mở + label_configuration_details: "Điểm cuối và chứng chỉ của nhà cung cấp danh tính" + label_configuration_encryption: "Chữ ký và mã hóa" + label_add_new: Nhà cung cấp danh tính SAML mới + label_edit: Chỉnh sửa nhà cung cấp danh tính SAML %{name} + label_uid: Id người dùng nội bộ + label_mapping: lập bản đồ + label_requested_attribute_for: "Thuộc tính được yêu cầu cho: %{attribute}" + no_results_table: Chưa có nhà cung cấp danh tính SAML nào được xác định. + notice_created: Nhà cung cấp danh tính SAML mới đã được tạo thành công. + plural: Nhà cung cấp danh tính SAML + singular: Nhà cung cấp danh tính SAML + requested_attributes: Thuộc tính được yêu cầu attribute_mapping: Ánh xạ thuộc tính attribute_mapping_text: > - The following fields control which attributes provided by the SAML identity provider are used to provide user attributes in OpenProject + Các trường sau đây kiểm soát những thuộc tính do nhà cung cấp nhận dạng SAML cung cấp được sử dụng để cung cấp thuộc tính người dùng trong OpenProject metadata: - dialog: "This is the URL where the OpenProject SAML metadata is available. Optionally use it to configure your identity provider:" + dialog: "Đây là URL có sẵn siêu dữ liệu OpenProject SAML. Tùy chọn sử dụng nó để định cấu hình nhà cung cấp danh tính của bạn:" upsell: - title: "Single Sign-On (SSO) with SAML" - description: Connect OpenProject to a SAML identity provider + title: "Đăng nhập một lần (SSO) bằng SAML" + description: Kết nối OpenProject với nhà cung cấp danh tính SAML request_attributes: - title: 'Requested attributes' + title: 'Thuộc tính được yêu cầu' legend: > - These attributes are added to the SAML XML metadata to signify to the identify provider which attributes OpenProject requires. You may still need to explicitly configure your identity provider to send these attributes. Please refer to your IdP's documentation. - name: 'Requested attribute key' - format: 'Attribute format' + Các thuộc tính này được thêm vào siêu dữ liệu XML SAML để biểu thị cho nhà cung cấp nhận dạng những thuộc tính mà OpenProject yêu cầu. Bạn vẫn có thể cần định cấu hình rõ ràng nhà cung cấp danh tính của mình để gửi các thuộc tính này. Vui lòng tham khảo tài liệu của IdP của bạn. + name: 'Khóa thuộc tính được yêu cầu' + format: 'Định dạng thuộc tính' section_headers: - configuration: "Primary configuration" - attributes: "Thuộc tính" + configuration: "Cấu hình chính" + attributes: "thuộc tính" section_texts: - display_name: "Configure the display name of the SAML provider." - metadata: "Pre-fill configuration using a metadata URL or by pasting metadata XML" - metadata_form: "If your identity provider has a metadata endpoint or XML download, add it below to pre-fill the configuration." - metadata_form_banner: "Editing the metadata may override existing values in other sections. " - configuration: "Configure the endpoint URLs for the identity provider, certificates, and further SAML options." - configuration_metadata: "This information has been pre-filled using the supplied metadata. In most cases, they do not require editing." - encryption: "Configure assertion signatures and encryption for SAML requests and responses." - encryption_form: "You may optionally want to encrypt the assertion response, or have requests from OpenProject signed." - mapping: "Manually adjust the mapping between the SAML response and user attributes in OpenProject." - requested_attributes: "Define the set of attributes to be requested in the SAML request sent to your identity provider." - seeded_from_env: "This provider was seeded from the environment configuration. It cannot be edited." + display_name: "Định cấu hình tên hiển thị của nhà cung cấp SAML." + metadata: "Cấu hình điền trước bằng URL siêu dữ liệu hoặc bằng cách dán XML siêu dữ liệu" + metadata_form: "Nếu nhà cung cấp danh tính của bạn có điểm cuối siêu dữ liệu hoặc bản tải xuống XML, hãy thêm điểm cuối siêu dữ liệu đó vào bên dưới để điền trước cấu hình." + metadata_form_banner: "Việc chỉnh sửa siêu dữ liệu có thể ghi đè các giá trị hiện có trong các phần khác." + configuration: "Định cấu hình URL điểm cuối cho nhà cung cấp danh tính, chứng chỉ và các tùy chọn SAML khác." + configuration_metadata: "Thông tin này đã được điền trước bằng siêu dữ liệu được cung cấp. Trong hầu hết các trường hợp, chúng không yêu cầu chỉnh sửa." + encryption: "Định cấu hình chữ ký xác nhận và mã hóa cho các yêu cầu và phản hồi SAML." + encryption_form: "Bạn có thể tùy ý muốn mã hóa phản hồi xác nhận hoặc yêu cầu các yêu cầu từ OpenProject được ký." + mapping: "Điều chỉnh ánh xạ theo cách thủ công giữa phản hồi SAML và thuộc tính người dùng trong OpenProject." + requested_attributes: "Xác định tập hợp thuộc tính cần được yêu cầu trong yêu cầu SAML được gửi tới nhà cung cấp danh tính của bạn." + seeded_from_env: "Nhà cung cấp này được chọn từ cấu hình môi trường. Nó không thể được chỉnh sửa." settings: - metadata_none: "I don't have metadata" - metadata_url: "Siêu dữ liệu URL" - metadata_xml: "Metadata XML" + metadata_none: "Tôi không có siêu dữ liệu" + metadata_url: "URL siêu dữ liệu" + metadata_xml: "XML siêu dữ liệu" instructions: documentation_link: > - Please refer to our [documentation on configuring SAML providers](docs_url) for more information on these configuration options. + Vui lòng tham khảo [documentation on configuring SAML providers](docs_url) của chúng tôi để biết thêm thông tin về các tùy chọn cấu hình này. display_name: > - The name of the provider. This will be displayed as the login button and in the list of providers. + Tên của nhà cung cấp. Điều này sẽ được hiển thị dưới dạng nút đăng nhập và trong danh sách các nhà cung cấp. metadata_none: > - Your identity provider does not have a metadata endpoint or XML download option. You can configure it manually. + Nhà cung cấp danh tính của bạn không có điểm cuối siêu dữ liệu hoặc tùy chọn tải xuống XML. Bạn có thể cấu hình nó bằng tay. metadata_url: > - Your identity provider provides a metadata URL. + Nhà cung cấp danh tính của bạn cung cấp URL siêu dữ liệu. metadata_xml: > - Your identity provider provides a metadata XML download. + Nhà cung cấp danh tính của bạn cung cấp bản tải xuống XML siêu dữ liệu. limit_self_registration: > - Nếu được bật, người dùng chỉ có thể đăng ký bằng nhà cung cấp này nếu cài đặt đăng ký tự động cho phép. + Nếu được bật, người dùng chỉ có thể đăng ký bằng nhà cung cấp này nếu cài đặt tự đăng ký cho phép. sp_entity_id: > - The entity ID of the service provider (SP). Sometimes also referred to as Audience. This is the unique client identifier of the OpenProject instance. + ID thực thể của nhà cung cấp dịch vụ (SP). Đôi khi còn được gọi là Khán giả. Đây là mã định danh khách hàng duy nhất của phiên bản OpenProject. idp_sso_service_url: > - The URL of the identity provider login endpoint. + URL của điểm cuối đăng nhập nhà cung cấp danh tính. idp_slo_service_url: > - The URL of the identity provider logout endpoint. + URL của điểm cuối đăng xuất của nhà cung cấp danh tính. idp_cert: > - Enter the X509 PEM-formatted public certificate of the identity provider. You can enter multiple certificates by separating them with a newline. + Nhập chứng chỉ công khai có định dạng X509 PEM của nhà cung cấp danh tính. Bạn có thể nhập nhiều chứng chỉ bằng cách phân tách chúng bằng dòng mới. name_identifier_format: > - Set the name identifier format to be used for the SAML assertion. + Đặt định dạng định danh tên sẽ được sử dụng cho xác nhận SAML. sp_metadata_endpoint: > - This is the URL where the OpenProject SAML metadata is available. Optionally use it to configure your identity provider. + Đây là URL có sẵn siêu dữ liệu OpenProject SAML. Tùy chọn sử dụng nó để định cấu hình nhà cung cấp danh tính của bạn. mapping: > - Configure the mapping between the SAML response and user attributes in OpenProject. You can configure multiple attribute names to look for. OpenProject will choose the first available attribute from the SAML response. + Định cấu hình ánh xạ giữa phản hồi SAML và thuộc tính người dùng trong OpenProject. Bạn có thể định cấu hình nhiều tên thuộc tính để tìm kiếm. OpenProject sẽ chọn thuộc tính có sẵn đầu tiên từ phản hồi SAML. mapping_login: > - SAML attributes from the response used for the login. + Thuộc tính SAML từ phản hồi được sử dụng để đăng nhập. mapping_mail: > - SAML attributes from the response used for the email of the user. + Thuộc tính SAML từ phản hồi được sử dụng cho email của người dùng. mapping_firstname: > - SAML attributes from the response used for the given name. + Thuộc tính SAML từ phản hồi được sử dụng cho tên đã cho. mapping_lastname: > - SAML attributes from the response used for the last name. + Thuộc tính SAML từ phản hồi được sử dụng cho họ. mapping_uid: > - SAML attribute to use for the internal user ID. Leave empty to use the name_id attribute instead + Thuộc tính SAML để sử dụng cho ID người dùng nội bộ. Để trống để sử dụng thuộc tính name_id thay thế request_uid: > - SAML attribute to request for the internal user ID. By default, the name_id will be used for this field. + Thuộc tính SAML để yêu cầu ID người dùng nội bộ. Theo mặc định, name_id sẽ được sử dụng cho trường này. requested_attributes: > - These attributes are added to the SAML request XML to communicate to the identity provider which attributes OpenProject requires. + Các thuộc tính này được thêm vào XML yêu cầu SAML để liên lạc với nhà cung cấp danh tính mà thuộc tính OpenProject yêu cầu. requested_format: > - The format of the requested attribute. This is used to specify the format of the attribute in the SAML request. Please see [documentation on configuring requested attributes](docs_url) for more information. + Định dạng của thuộc tính được yêu cầu. Điều này được sử dụng để chỉ định định dạng của thuộc tính trong yêu cầu SAML. Vui lòng xem [documentation on configuring requested attributes](docs_url) để biết thêm thông tin. authn_requests_signed: > - If checked, OpenProject will sign the SAML AuthnRequest. You will have to provide a signing certificate and private key using the fields below. + Nếu được chọn, OpenProject sẽ ký SAML AuthnRequest. Bạn sẽ phải cung cấp chứng chỉ ký và khóa riêng bằng cách sử dụng các trường bên dưới. want_assertions_signed: > - If checked, OpenProject will required signed responses from the identity provider using its own certificate keypair. OpenProject will verify the signature against the certificate from the basic configuration section. + Nếu được chọn, OpenProject sẽ yêu cầu phản hồi có chữ ký từ nhà cung cấp danh tính bằng cặp khóa chứng chỉ của chính họ. OpenProject sẽ xác minh chữ ký đối với chứng chỉ từ phần cấu hình cơ bản. want_assertions_encrypted: > - If enabled, require the identity provider to encrypt the assertion response using the certificate pair that you provide. + Nếu được bật, hãy yêu cầu nhà cung cấp danh tính mã hóa phản hồi xác nhận bằng cặp chứng chỉ mà bạn cung cấp. certificate: > - Enter the X509 PEM-formatted certificate used by OpenProject for signing SAML requests. + Nhập chứng chỉ có định dạng X509 PEM được OpenProject sử dụng để ký các yêu cầu SAML. private_key: > - Enter the X509 PEM-formatted private key for the above certificate. This needs to be an RSA private key. + Nhập khóa riêng có định dạng X509 PEM cho chứng chỉ trên. Đây cần phải là khóa riêng RSA. signature_method: > - Select the signature algorithm to use for the SAML request signature performed by OpenProject (Default: %{default_option}). + Chọn thuật toán chữ ký để sử dụng cho chữ ký yêu cầu SAML do OpenProject thực hiện (Mặc định: %{default_option}). digest_method: > - Select the digest algorithm to use for the SAML request signature performed by OpenProject (Default: %{default_option}). + Chọn thuật toán tóm tắt để sử dụng cho chữ ký yêu cầu SAML do OpenProject thực hiện (Mặc định: %{default_option}). icon: > - Optionally provide a public URL to an icon graphic that will be displayed next to the provider name. + Tùy chọn cung cấp URL công khai cho đồ họa biểu tượng sẽ được hiển thị bên cạnh tên nhà cung cấp. metadata_for_idp: > - This information might be requested by your SAML identity provider. + Thông tin này có thể được yêu cầu bởi nhà cung cấp danh tính SAML của bạn. diff --git a/modules/avatars/config/locales/crowdin/js-vi.yml b/modules/avatars/config/locales/crowdin/js-vi.yml index 3f75e484d98..0637d75f4a0 100644 --- a/modules/avatars/config/locales/crowdin/js-vi.yml +++ b/modules/avatars/config/locales/crowdin/js-vi.yml @@ -2,13 +2,14 @@ vi: js: label_preview: 'Xem trước' - button_update: 'Cập Nhật' + button_update: 'cập nhật' avatars: - label_choose_avatar: "Chọn hình đại diện từ tập tin" + label_choose_avatar: "Chọn Avatar từ tập tin" uploading_avatar: "Đang tải hình đại diện của bạn" text_upload_instructions: | - Tải lên hình đại diện tùy chỉnh của bạn, kích thước 128x128 điểm ảnh. Kích thước lớn hơn sẽ bị giảm chất lượng và thu nhỏ để phù hợp. Ảnh xem trước của hình đại diện sẽ hiện lên trước khi tải lên khi mà bạn chọn ảnh. + Tải lên hình đại diện tùy chỉnh của riêng bạn có kích thước 128 x 128 pixel. Các tệp lớn hơn sẽ được thay đổi kích thước và cắt xén cho phù hợp. + Bản xem trước hình đại diện của bạn sẽ được hiển thị trước khi tải lên, sau khi bạn chọn hình ảnh. error_image_too_large: "Hình ảnh quá lớn" wrong_file_format: "Định dạng ảnh được chấp nhận: jpg, png, gif" - empty_file_error: "Xin hãy tải lên hình ảnh đúng định dạng(jpg, png, gif)" + empty_file_error: "Vui lòng tải lên hình ảnh hợp lệ (jpg, png, gif)" diff --git a/modules/avatars/config/locales/crowdin/vi.yml b/modules/avatars/config/locales/crowdin/vi.yml index 8314ca943f0..f9397bcc551 100644 --- a/modules/avatars/config/locales/crowdin/vi.yml +++ b/modules/avatars/config/locales/crowdin/vi.yml @@ -1,9 +1,9 @@ #English strings go here vi: plugin_openproject_avatars: - name: "Ảnh đại diện" + name: "hình đại diện" description: >- - Plugin này cho phép người dùng OpenProject tải lên một bức ảnh để sử dụng làm ảnh đại diện hoặc sử dụng các hình ảnh đã đăng ký từ Gravatar. + Plugin này cho phép người dùng OpenProject tải lên hình ảnh để sử dụng làm hình đại diện hoặc sử dụng hình ảnh đã đăng ký từ Gravatar. label_avatar: "Hình đại diện" label_avatar_plural: "Các hình đại diện" label_current_avatar: "Hình đại diện hiện tại" @@ -17,7 +17,7 @@ vi: wrong_file_format: "Định dạng ảnh được chấp nhận: jpg, png, gif" empty_file_error: "Xin hãy tải lên hình ảnh đúng định dạng(jpg, png, gif)" avatars: - label_avatar: "Ảnh đại diện" + label_avatar: "hình đại diện" label_gravatar: 'Nhóm ảnh đại diện' label_current_avatar: 'Hình đại diện hiện tại' label_local_avatar: 'Tùy chỉnh hình đại diện' diff --git a/modules/backlogs/config/locales/crowdin/js-vi.yml b/modules/backlogs/config/locales/crowdin/js-vi.yml index 6a60480b023..1cad19bcb64 100644 --- a/modules/backlogs/config/locales/crowdin/js-vi.yml +++ b/modules/backlogs/config/locales/crowdin/js-vi.yml @@ -23,4 +23,4 @@ vi: js: work_packages: properties: - storyPoints: "Điểm câu chuyện" + storyPoints: "Điểm cốt truyện" diff --git a/modules/backlogs/config/locales/crowdin/pl.yml b/modules/backlogs/config/locales/crowdin/pl.yml index 086979cbf0d..ed8f08b125b 100644 --- a/modules/backlogs/config/locales/crowdin/pl.yml +++ b/modules/backlogs/config/locales/crowdin/pl.yml @@ -41,7 +41,7 @@ pl: sprint: cannot_end_before_it_starts: "Sprint nie może się skończyć przed swoim rozpoczęciem." attributes: - task_type: "Task type" + task_type: "Typ zadania" backlogs: add_new_story: "Nowe Story" any: "którekolwiek" @@ -105,7 +105,7 @@ pl: errors: attributes: task_type: - cannot_be_story_type: "can not also be a story type" + cannot_be_story_type: "nie może być również typem historii" error_intro_plural: "Wystąpiły następujące błędy:" error_intro_singular: "Wystąpił następujący błąd:" error_outro: "Popraw błędy powyżej przed ponownym złożeniem." @@ -129,10 +129,10 @@ pl: label_points_burn_up: "W górę" label_product_backlog: "backlog produktu" label_select_all: "Wybierz wszystko" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" + label_select_type: "Wybierz typ" + label_select_types: "Wybierz typy" + label_selected_type: "Wybrany typ" + label_selected_types: "Wybrane typy" label_sprint_backlog: "backlog sprintu" label_sprint_cards: "Eksportuj karty" label_sprint_impediments: "Przeszkody sprintu" @@ -170,6 +170,6 @@ pl: version_settings_display_option_none: "żaden" version_settings_display_option_right: "w prawo" setting_plugin_openproject_backlogs_story_types_caption: | - Types treated as backlog stories (e.g., Feature, User story). Must differ from task type. + Typy traktowane jako historie backlogu (np. Funkcja, Wpis użytkownika). Musi różnić się od typu zadania. setting_plugin_openproject_backlogs_task_type_caption: | - Type used for story tasks. Must differ from story types. + Typ używany w zadaniach historii. Musi różnić się od typów historii. diff --git a/modules/backlogs/config/locales/crowdin/vi.yml b/modules/backlogs/config/locales/crowdin/vi.yml index 59fb16432cc..a5c209cbc4e 100644 --- a/modules/backlogs/config/locales/crowdin/vi.yml +++ b/modules/backlogs/config/locales/crowdin/vi.yml @@ -21,13 +21,13 @@ #++ vi: plugin_openproject_backlogs: - name: "OpenProject Backlogs" - description: "Mô-đun này bổ sung các tính năng cho phép các nhóm agile làm việc với OpenProject trong các dự án Scrum." + name: "Tồn đọng dự án mở" + description: "Mô-đun này bổ sung các tính năng cho phép các nhóm linh hoạt làm việc với OpenProject trong các dự án Scrum." activerecord: attributes: work_package: position: "Vị trí" - story_points: "Điểm câu chuyện" + story_points: "Điểm cốt truyện" backlogs_work_package_type: "Loại Backlog" errors: models: @@ -41,13 +41,13 @@ vi: sprint: cannot_end_before_it_starts: "Sprint không thể kết thúc trước khi nó bắt đầu." attributes: - task_type: "Task type" + task_type: "Loại nhiệm vụ" backlogs: add_new_story: "Câu chuyện mới" any: "bất kỳ" backlog_settings: "Lịch sử cài đặt" burndown_graph: "Đồ thị Burndown" - card_paper_size: "Kích thước giấy cho in thẻ" + card_paper_size: "Kích thước giấy in thiệp" chart_options: "Tùy chọn biểu đồ" close: "Đóng" column_width: "Chiều rộng cột:" @@ -57,10 +57,10 @@ vi: hours: "Giờ" impediment: "Trở ngại" label_versions_default_fold_state: "Hiển thị các phiên bản \n" - caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." - work_package_is_closed: "Gói công việc đã hoàn thành, khi" - label_is_done_status: "Trạng thái %{status_name} có nghĩa đã hoàn thành" - no_burndown_data: "Không có dữ liệu biểu đồ giảm dần. Cần phải thiết lập ngày bắt đầu và kết thúc cao điểm." + caption_versions_default_fold_state: "Các phiên bản sẽ không được mở rộng theo mặc định khi xem hồ sơ tồn đọng. Mỗi cái phải được mở rộng bằng tay." + work_package_is_closed: "Gói công việc được thực hiện, khi" + label_is_done_status: "Trạng thái %{status_name} nghĩa là đã xong" + no_burndown_data: "Không có sẵn dữ liệu về sự cố. Cần thiết phải ấn định ngày bắt đầu và ngày kết thúc của sprint." points: "Điểm" positions_could_not_be_rebuilt: "Không thể tạo lại vị trí." positions_rebuilt_successfully: "Tạo lại vị trí thành công." @@ -69,95 +69,95 @@ vi: rebuild_positions: "Xây dựng lại vị trí" remaining_hours: "Công việc còn lại" remaining_hours_ideal: "Công việc còn lại (lý tưởng)" - show_burndown_chart: "Biểu đồ giảm dần" + show_burndown_chart: "Biểu đồ đốt cháy" story: "Câu chuyện" - story_points: "Điểm câu chuyện" - story_points_ideal: "Điểm câu chuyện (lý tưởng)" + story_points: "Điểm cốt truyện" + story_points_ideal: "Điểm cốt truyện (lý tưởng)" task: "Nhiệm vụ" task_color: "Màu nhiệm vụ" - unassigned: "Chưa được phân công" + unassigned: "Chưa được chỉ định" user_preference: - header_backlogs: "Backlogs module" - button_update_backlogs: "Update backlogs module" + header_backlogs: "Mô-đun tồn đọng" + button_update_backlogs: "Cập nhật mô-đun tồn đọng" x_more: "%{count} thêm..." - backlogs_active: "đang hoạt động" + backlogs_active: "Đang hoạt động" backlogs_any: "bất kỳ" - backlogs_inactive: "Dự án không có hoạt động" - backlogs_points_burn_direction: "Điểm giảm lên/xuống" + backlogs_inactive: "Dự án không hiển thị hoạt động nào" + backlogs_points_burn_direction: "Điểm tăng/giảm" backlogs_product_backlog: "Product backlog" backlogs_product_backlog_is_empty: "Không có Product backlog" - backlogs_product_backlog_unsized: "Phần đầu của bảng nhiệm vụ tồn đọng với sản phẩm có những câu chuyện chưa được ước lượng kích thước" - backlogs_sizing_inconsistent: "Kích thước câu chuyện không khớp với ước lượng của chúng" - backlogs_sprint_notes_missing: "Cao điểm đã đóng mà không có ghi chép/nhận xét" - backlogs_sprint_unestimated: "Cao điểm đã đóng hoặc đang hoạt động có những câu chuyện chưa được ước lượng" - backlogs_sprint_unsized: "Dự án có những câu chuyện trên các cao điểm đang hoạt động hoặc đã đóng gần đây mà chưa được ước lượng" - backlogs_sprints: "Cao điểm" - backlogs_story: "Câu chuyện" + backlogs_product_backlog_unsized: "Phần trên cùng của sản phẩm tồn đọng có các cốt truyện chưa được định cỡ" + backlogs_sizing_inconsistent: "Kích thước cốt truyện khác nhau so với ước tính của họ" + backlogs_sprint_notes_missing: "Chạy nước rút đã đóng mà không có ghi chú hồi tưởng/đánh giá" + backlogs_sprint_unestimated: "Chạy nước rút đã đóng hoặc đang hoạt động với các cốt truyện chưa được ước tính" + backlogs_sprint_unsized: "Dự án có các câu chuyện về các lần chạy nước rút đang hoạt động hoặc đã đóng gần đây nhưng không được định cỡ" + backlogs_sprints: "Chạy nước rút" + backlogs_story: "Cốt truyện" backlogs_story_type: "Loại câu truyện tóm tắt" backlogs_task: "Nhiệm vụ" backlogs_task_type: "Loại công việc" backlogs_velocity_missing: "Không có tốc độ hoàn thành nào được tính cho dự án này" - backlogs_velocity_varies: "Tốc độ thay đổi đáng kể qua các đợt cao điểm" - backlogs_wiki_template: "Mẫu cho trang wiki cao điểm" - backlogs_empty_title: "Không có phiên bản nào được xác định để sử dụng trong các nhiệm vụ tồn đọng" - backlogs_empty_action_text: "Để bắt đầu vớicác nhiệm vụ tồn đọng, hãy tạo một phiên bản mới và gán nó vào một cột các nhiệm vụ tồn đọng" + backlogs_velocity_varies: "Vận tốc thay đổi đáng kể qua các lần chạy nước rút" + backlogs_wiki_template: "Mẫu cho trang wiki chạy nước rút" + backlogs_empty_title: "Không có phiên bản nào được xác định sẽ được sử dụng trong các hồ sơ tồn đọng" + backlogs_empty_action_text: "Để bắt đầu xử lý tồn đọng, hãy tạo một phiên bản mới và gán nó vào cột tồn đọng." button_edit_wiki: "Sửa trang wiki" errors: attributes: task_type: - cannot_be_story_type: "can not also be a story type" + cannot_be_story_type: "cũng không thể là một loại cốt truyện" error_intro_plural: "Các lỗi đã gặp phải:" - error_intro_singular: "Lỗi sau đã được gặp phải:" + error_intro_singular: "Đã gặp phải lỗi sau:" error_outro: "Vui lòng sửa các lỗi trên trước khi gửi lại." event_sprint_description: "%{summary}: %{url}\n%{description}" event_sprint_summary: "%{project}: %{summary}" ideal: "lý tưởng" inclusion: "không được bao gồm trong danh sách" label_back_to_project: "Quay lại trang dự án" - label_backlog: "Nhiệm vụ tồn đọng" - label_backlogs: "Các nhiệm vụ tồn đọng" + label_backlog: "Tồn đọng" + label_backlogs: "tồn đọng" label_backlogs_unconfigured: "Bạn chưa cấu hình Bảng nhiệm vụ tồn đọng. Vui lòng vào %{administration} > %{plugins}, sau đó nhấp vào liên kết %{configure} cho gắn thêm này. Khi bạn đã thiết lập các trường, quay lại trang này để bắt đầu sử dụng công cụ." label_blocks_ids: "ID của các work package bị chặn" - label_burndown: "Giảm dần" - label_column_in_backlog: "Cột trong các nhiệm vụ tồn đọng" + label_burndown: "Đốt cháy" + label_column_in_backlog: "Cột tồn đọng" label_hours: "hours" - label_work_package_hierarchy: "Hệ thống phân cấp gói công việc" - label_master_backlog: "Các nhiệm vụ tồn đọng chính" + label_work_package_hierarchy: "Gói công việc" + label_master_backlog: "Tồn đọng chính" label_not_prioritized: "không được ưu tiên" label_points: "điểm" label_points_burn_down: "Xuống" - label_points_burn_up: "Lên" + label_points_burn_up: "lên" label_product_backlog: "tồn đọng sản phẩm" label_select_all: "Chọn tất cả" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" - label_sprint_backlog: "Cao điểm nhiệm vụ tồn đọng" + label_select_type: "Chọn một loại" + label_select_types: "Chọn loại" + label_selected_type: "Loại đã chọn" + label_selected_types: "Các loại đã chọn" + label_sprint_backlog: "tồn đọng nước rút" label_sprint_cards: "Xuất thẻ" - label_sprint_impediments: "Vướng mắc của Cao điểm" - label_sprint_name: "Cao điểm \"%{name}\"" - label_sprint_velocity: "Tốc độ %{velocity}, dựa trên %{sprints} cao điểm với trung bình %{days} ngày" + label_sprint_impediments: "Trở ngại nước rút" + label_sprint_name: "Chạy nước rút \"%{name}\"" + label_sprint_velocity: "Vận tốc %{velocity}, dựa trên %{sprints} lần chạy nước rút với trung bình %{days} ngày" label_stories: "Những câu chuyện" label_stories_tasks: "Câu chuyện/Nhiệm vụ" label_task_board: "Bảng nhiệm vụ" - label_version_setting: "Các phiên bản" + label_version_setting: "phiên bản" label_version: '0886055830 ' - label_webcal: "Webcal Feed" - label_wiki: "Wiki" + label_webcal: "Nguồn cấp dữ liệu Webcal" + label_wiki: "wiki" permission_view_master_backlog: "Xem tồn đọng chính" permission_view_taskboards: "Xem bảng tác vụ" permission_select_done_statuses: "Chọn trạng thái hoàn thành" - permission_update_sprints: "Cập nhật các cao điểm" - points_accepted: "điểm đã chấp nhận" - points_committed: "điểm đã cam kết" - points_resolved: "điểm đã giải quyết" + permission_update_sprints: "Cập nhật các lần chạy nước rút" + points_accepted: "điểm được chấp nhận" + points_committed: "số điểm đã cam kết" + points_resolved: "điểm đã được giải quyết" points_to_accept: "điểm không được chấp nhận" points_to_resolve: "điểm chưa được giải quyết" - project_module_backlogs: "Bảng nhiệm vụ tồn đọng" + project_module_backlogs: "tồn đọng" rb_label_copy_tasks: "Sao chép work packages" rb_label_copy_tasks_all: "Toàn bộ" - rb_label_copy_tasks_none: "Không có" + rb_label_copy_tasks_none: "không có" rb_label_copy_tasks_open: "Mở" rb_label_link_to_original: "Bao gồm liên kết đến câu chuyện gốc" remaining_hours: "công việc còn lại" @@ -165,11 +165,11 @@ vi: required_burn_rate_points: "tốc độ ghi yêu cầu (điểm)" todo_work_package_description: "%{summary}: %{url}\n%{description}" todo_work_package_summary: "%{type}: %{summary}" - version_settings_display_label: "Cột trong nhiệm vụ tồn đọng" + version_settings_display_label: "Cột tồn đọng" version_settings_display_option_left: "trái" version_settings_display_option_none: "không" version_settings_display_option_right: "phải" setting_plugin_openproject_backlogs_story_types_caption: | - Types treated as backlog stories (e.g., Feature, User story). Must differ from task type. + Các loại được coi là cốt truyện tồn đọng (ví dụ: Tính năng, Cốt truyện của người dùng). Phải khác với loại nhiệm vụ. setting_plugin_openproject_backlogs_task_type_caption: | - Type used for story tasks. Must differ from story types. + Loại được sử dụng cho nhiệm vụ câu chuyện. Phải khác với các loại câu chuyện. diff --git a/modules/bim/config/locales/crowdin/js-vi.yml b/modules/bim/config/locales/crowdin/js-vi.yml index aeb05abc2b8..c464812a955 100644 --- a/modules/bim/config/locales/crowdin/js-vi.yml +++ b/modules/bim/config/locales/crowdin/js-vi.yml @@ -3,15 +3,15 @@ vi: js: bcf: label_bcf: 'BCF' - import: 'Nhập khẩu' - import_bcf_xml_file: 'Nhập khẩu tệp BCF XML (Phiên bản BCF 2.1)' - export: 'Xuất' - export_bcf_xml_file: 'Xuất khẩu tệp BCF XML (Phiên bản BCF 2.1)' + import: 'nhập khẩu' + import_bcf_xml_file: 'Nhập tệp XML BCF (BCF phiên bản 2.1)' + export: 'xuất khẩu' + export_bcf_xml_file: 'Xuất tệp XML BCF (BCF phiên bản 2.1)' viewpoint: 'Góc nhìn' add_viewpoint: 'Thêm góc nhìn' show_viewpoint: 'Hiện góc nhìn' delete_viewpoint: 'Xóa góc nhìn' - management: 'Quản lý BCF' + management: 'quản lý BCF' refresh: 'Làm mới' refresh_work_package: 'Làm mới gói công việc' ifc_models: @@ -19,11 +19,11 @@ vi: use_this_link_to_manage: "Dùng link này để tải lên và quản lý mô hình IFC" keyboard_input_disabled: "Trình xem không có quyền điều khiển bàn phím. Kích vào \"trình xem\" để cấp quyền điều khiển bàn phím cho \"trình xem\"" models: - ifc_models: 'Mô hình IFC' + ifc_models: 'mô hình IFC' views: viewer: 'Trình xem' split: 'Trình xem và bảng' split_cards: 'Trình xem và thẻ' revit: - revit_add_in: "Tiện ích bổ sung Revit" - revit_add_in_settings: "Cài đặt tiện ích bổ sung Revit" + revit_add_in: "Bổ trợ Revit" + revit_add_in_settings: "Cài đặt bổ trợ Revit" diff --git a/modules/bim/config/locales/crowdin/vi.seeders.yml b/modules/bim/config/locales/crowdin/vi.seeders.yml index 56128f0f0b5..80dd70af65a 100644 --- a/modules/bim/config/locales/crowdin/vi.seeders.yml +++ b/modules/bim/config/locales/crowdin/vi.seeders.yml @@ -9,108 +9,108 @@ vi: item_0: name: Thấp item_1: - name: Bình Thường + name: Bình thường item_2: - name: Cao + name: cao item_3: - name: Nghiêm trọng + name: quan trọng statuses: item_0: name: Mới item_1: - name: Đang xử lý + name: Đang tiến hành item_2: - name: Đã giải quyết + name: đã giải quyết item_3: - name: Đã đóng + name: đóng cửa time_entry_activities: item_0: - name: Quản lý + name: quản lý item_1: name: Đặc điểm kỹ thuật item_2: - name: Khác + name: khác types: item_0: name: Nhiệm vụ item_1: - name: Milestone + name: cột mốc quan trọng item_2: - name: Giai đoạn + name: giai đoạn item_3: - name: Vấn đề + name: vấn đề item_4: - name: Lưu ý + name: Nhận xét item_5: name: Yêu cầu item_6: - name: Xung đột + name: Đụng độ global_queries: item_0: - name: 'Bảng nhúng: cấp dưới' + name: 'Bảng nhúng: Trẻ em' type_configuration: item_0: form_configuration: item_0: - group_name: Con + group_name: bọn trẻ groups: item_0: - name: Các kiến trúc sư + name: kiến trúc sư item_1: name: Điều phối viên BIM item_2: - name: Quản lý BIM + name: Người quản lý BIM item_3: - name: Mô hình hóa BIM + name: Người lập mô hình BIM item_4: - name: Điều phối viên BIM hàng đầu + name: Điều phối viên BIM chính item_5: name: Kỹ sư MEP item_6: - name: Người lập kế hoạch + name: người lập kế hoạch item_7: name: Kỹ sư kết cấu welcome: - title: Chào mừng đến với phiên bản BIM của OpenProject! + title: Chào mừng bạn đến với phiên bản OpenProject BIM! text: | - Xem qua các dự án mẫu để bắt đầu với một số ví dụ. + Kiểm tra các dự án demo để bắt đầu với một số ví dụ. - * [(Demo) Dự án xây dựng]({{opSetting:base_url}}/projects/demo-construction-project): Lập kế hoạch, quy trình BIM, quản lý BCF và xây dựng, tất cả trong một cái nhìn. - * [(Demo) Lập kế hoạch & xây dựng]({{opSetting:base_url}}/projects/demo-planning-constructing-project): Lập kế hoạch và quản lý xây dựng theo cách truyền thống. - * [(Demo) Dự án BIM]({{opSetting:base_url}}/projects/demo-bim-project): Quy trình và điều phối BIM. - * [(Demo) Quản lý BCF]({{opSetting:base_url}}/projects/demo-bcf-management-project): Quản lý BCF. + * [(Demo) Construction project]({{opSetting:base_url}}/projects/demo-construction-project): Lập kế hoạch, quy trình BIM, quản lý BCF và xây dựng, tất cả chỉ trong nháy mắt. + * [(Demo) Planning & constructing]({{opSetting:base_url}}/projects/demo-planning-constructing-project): Quy hoạch cổ điển và quản lý xây dựng. + * [(Demo) Bim project]({{opSetting:base_url}}/projects/demo-bim-project): Quy trình và điều phối BIM. + * [(Demo) BCF management]({{opSetting:base_url}}/projects/demo-bcf-management-project): Quản lý BCF. - Bạn cũng có thể tạo một [dự án mới]({{opSetting:base_url}}/projects/new) trống. + Ngoài ra, bạn có thể tạo một khoảng trống [new project]({{opSetting:base_url}}/projects/new). - Đừng ngừng hợp tác. Với mã nguồn mở và tư duy mở. + Không bao giờ ngừng hợp tác. Với nguồn mở và tư duy cởi mở. - Bạn có thể thay đổi văn bản chào mừng này [tại đây]({{opSetting:base_url}}/admin/settings/general). + Bạn có thể thay đổi văn bản chào mừng này [here]({{opSetting:base_url}}/admin/settings/general). projects: demo-construction-project: name: "(Demo) Dự án xây dựng" - status_explanation: Tất cả các nhiệm vụ và các dự án con đều theo lịch trình. Những người tham gia đã biết nhiệm vụ của họ. Hệ thống đã được thiết lập hoàn toàn. - description: Đây là một tóm tắt ngắn gọn về các mục tiêu của dự án xây dựng mẫu này. + status_explanation: Tất cả các nhiệm vụ và tiểu dự án đều đúng tiến độ. Những người liên quan biết nhiệm vụ của họ. Hệ thống đã được thiết lập hoàn chỉnh. + description: Đây là bản tóm tắt ngắn gọn về các mục tiêu của dự án xây dựng demo này. news: item_0: title: Chào mừng đến với dự án demo của bạn summary: | Chúng tôi rất vui vì bạn đã tham gia. - Trong mô-đun này, bạn có thể truyền đạt tin tức dự án đến các thành viên trong nhóm của bạn. + Trong mô-đun này, bạn có thể truyền đạt tin tức dự án cho các thành viên trong nhóm của mình. description: Tin tức thực tế categories: - item_0: Danh mục 1 (cần thay đổi trong cài đặt Dự án) + item_0: Loại 1 (sẽ được thay đổi trong cài đặt Dự án) queries: item_0: - name: Kế hoạch dự án + name: kế hoạch dự án item_1: - name: Các mốc thời gian + name: Các cột mốc quan trọng item_2: name: Nhiệm vụ item_3: - name: Lịch trình nhóm + name: Người lập kế hoạch nhóm boards: bcf: - name: Quy trình kéo và thả đơn giản + name: Quy trình làm việc kéo thả đơn giản project-overview: widgets: item_0: @@ -141,35 +141,35 @@ vi: Vui lòng cho chúng tôi biết nếu bạn có bất kỳ câu hỏi nào hoặc cần hỗ trợ. Liên hệ với chúng tôi: [support\[at\]openproject.com](mailto:support@openproject.com). item_4: options: - name: Thành viên + name: thành viên item_5: options: name: Work Packages item_6: options: - name: Các mốc thời gian + name: Các cột mốc quan trọng demo-planning-constructing-project: - name: "(Demo) Lập kế hoạch & xây dựng" - status_explanation: Tất cả các nhiệm vụ đang theo đúng tiến độ. Những người liên quan biết nhiệm vụ của họ. Hệ thống đã được thiết lập hoàn chỉnh. - description: Đây là một tóm tắt ngắn gọn về các mục tiêu của dự án lập kế hoạch và xây dựng demo này. + name: "(Demo) Quy hoạch & xây dựng" + status_explanation: Mọi công việc đều đúng tiến độ. Những người liên quan biết nhiệm vụ của họ. Hệ thống đã được thiết lập hoàn chỉnh. + description: Đây là bản tóm tắt ngắn gọn về các mục tiêu của dự án lập kế hoạch và xây dựng demo này. news: item_0: title: Chào mừng đến với dự án demo của bạn summary: | Chúng tôi rất vui vì bạn đã tham gia. - Trong mô-đun này, bạn có thể truyền đạt tin tức dự án đến các thành viên trong nhóm của bạn. + Trong mô-đun này, bạn có thể truyền đạt tin tức dự án cho các thành viên trong nhóm của mình. description: Tin tức hiện tại categories: - item_0: Danh mục 1 (cần thay đổi trong cài đặt Dự án) + item_0: Loại 1 (sẽ được thay đổi trong cài đặt Dự án) queries: item_0: - name: Kế hoạch dự án + name: kế hoạch dự án item_1: - name: Các mốc thời gian + name: Các cột mốc quan trọng item_2: name: Nhiệm vụ item_3: - name: Lịch trình nhóm + name: Người lập kế hoạch nhóm project-overview: widgets: item_0: @@ -196,113 +196,113 @@ vi: Vui lòng cho chúng tôi biết nếu bạn có bất kỳ câu hỏi nào hoặc cần hỗ trợ. Liên hệ với chúng tôi: [support\[at\]openproject.com](mailto:support@openproject.com). item_4: options: - name: Thành viên + name: thành viên item_5: options: name: Work Packages item_6: options: - name: Các mốc thời gian + name: Các cột mốc quan trọng work_packages: item_0: - subject: Khởi động dự án xây dựng + subject: Dự án khởi công dự án xây dựng description: |- Khởi động dự án đánh dấu sự bắt đầu của dự án trong công ty của bạn. Mọi người tham gia vào dự án này nên được mời đến buổi khởi động để nhận thông tin ban đầu. Bước tiếp theo có thể là kiểm tra lịch trình và điều chỉnh các cuộc hẹn, bằng cách xem [Biểu đồ Gantt]({{opSetting:base_url}}/projects/demo-construction-project/work_packages?query_props=%7B%22c%22%3A%5B%22id%22%2C%22subject%22%2C%22startDate%22%2C%22dueDate%22%5D%2C%22tv%22%3Atrue%2C%22tzl%22%3A%22weeks%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22status%22%2C%22o%22%3A%22o%22%2C%22v%22%3A%5B%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%7D). item_1: subject: Đánh giá cơ bản - description: Kiểu này về mặt phân cấp là kiểu cha của kiểu "Clash" và "Request", do đó thể hiện một lưu ý chung. + description: Loại này được phân cấp theo cấp bậc cha của các loại "Clash" và "Request", do đó đại diện cho một ghi chú chung. children: item_0: subject: Thu thập thông tin dự án đầu tiên description: |- ## Mục tiêu - * Xác định các nhiệm vụ dựa trên nhu cầu của khách hàng - * Định nghĩa khung thời gian và ước lượng chi phí + * Xác định nhiệm vụ dựa trên nhu cầu của khách hàng + * Khung thời gian và ước tính chi phí sẽ được xác định ## Mô tả - * Xác định nhu cầu của khách hàng bằng cách tổ chức một buổi workshop với khách hàng - * Mỗi nhu cầu sẽ đại diện cho một nhiệm vụ với các gói công việc tương ứng - * Đưa ra ước lượng chi phí và khung thời gian + * Xác định nhu cầu của khách hàng bằng cách tổ chức một buổi hội thảo với họ + * Mỗi nhu cầu sẽ đại diện cho một nhiệm vụ với các gói công việc tương ứng + * Lập dự toán chi phí và khung thời gian item_1: subject: Tóm tắt kết quả description: |- ## Mục tiêu - * Tạo một cái nhìn tổng quan hữu ích về các kết quả - * Kiểm tra những gì đã được thực hiện và tóm tắt các kết quả - * Thông báo tất cả các kết quả liên quan với khách hàng - * Xác định các điều kiện cơ bản của dự án + * Tạo một cái nhìn tổng quan hữu ích về kết quả + * Kiểm tra những gì đã được thực hiện và tóm tắt kết quả + * Truyền đạt tất cả các kết quả có liên quan với khách hàng + * Xác định các điều kiện biên cơ bản của dự án ## Mô tả - * Mỗi chủ đề sẽ có cái nhìn tổng quan riêng, sẽ được sử dụng như một danh mục kết quả - * Cái nhìn tổng quan này thông báo cho tất cả các bên liên quan về các quyết định đã được đưa ra - * ... + * Mỗi chủ đề có cái nhìn tổng quan riêng và sẽ được sử dụng làm danh mục kết quả + * Tổng quan này thông báo cho tất cả người tham gia về các quyết định được đưa ra + * ... item_2: subject: Kết thúc đánh giá cơ bản - description: Kiểu này về mặt phân cấp là kiểu cha của kiểu "Clash" và "Request", do đó thể hiện một lưu ý chung. + description: Loại này được phân cấp theo cấp bậc cha của các loại "Clash" và "Request", do đó đại diện cho một ghi chú chung. item_2: subject: Lập kế hoạch sơ bộ - description: Kiểu này về mặt phân cấp là kiểu cha của kiểu "Clash" và "Request", do đó thể hiện một lưu ý chung. + description: Loại này được phân cấp theo cấp bậc cha của các loại "Clash" và "Request", do đó đại diện cho một ghi chú chung. children: item_0: - subject: Phát triển bản nháp đầu tiên + subject: Phát triển dự thảo đầu tiên description: |- ## Mục tiêu - * Tạo một cái nhìn tổng quan hữu ích về các kết quả - * Kiểm tra những gì đã được thực hiện và tóm tắt các kết quả - * Thông báo tất cả các kết quả liên quan với khách hàng - * Xác định các điều kiện cơ bản của dự án + * Tạo một cái nhìn tổng quan hữu ích về kết quả + * Kiểm tra những gì đã được thực hiện và tóm tắt kết quả + * Truyền đạt tất cả các kết quả có liên quan với khách hàng + * Xác định các điều kiện biên cơ bản của dự án ## Mô tả - * Mỗi chủ đề sẽ có cái nhìn tổng quan riêng, sẽ được sử dụng như một danh mục kết quả - * Cái nhìn tổng quan này thông báo cho tất cả các bên liên quan về các quyết định đã được đưa ra - * ... + * Mỗi chủ đề có cái nhìn tổng quan riêng và sẽ được sử dụng làm danh mục kết quả + * Tổng quan này thông báo cho tất cả người tham gia về các quyết định được đưa ra + * ... item_1: subject: Tóm tắt kết quả description: |- ## Mục tiêu - * Tạo một cái nhìn tổng quan hữu ích về các kết quả - * Kiểm tra những gì đã được thực hiện và tóm tắt các kết quả - * Thông báo tất cả các kết quả liên quan với khách hàng - * Xác định các điều kiện cơ bản của dự án + * Tạo một cái nhìn tổng quan hữu ích về kết quả + * Kiểm tra những gì đã được thực hiện và tóm tắt kết quả + * Truyền đạt tất cả các kết quả có liên quan với khách hàng + * Xác định các điều kiện biên cơ bản của dự án ## Mô tả - * Mỗi chủ đề sẽ có cái nhìn tổng quan riêng, sẽ được sử dụng như một danh mục kết quả - * Cái nhìn tổng quan này thông báo cho tất cả các bên liên quan về các quyết định đã được đưa ra - * ... + * Mỗi chủ đề có cái nhìn tổng quan riêng và sẽ được sử dụng làm danh mục kết quả + * Tổng quan này thông báo cho tất cả người tham gia về các quyết định được đưa ra + * ... item_3: - subject: Kết thúc lập kế hoạch sơ bộ - description: Kiểu này về mặt phân cấp là kiểu cha của kiểu "Clash" và "Request", do đó thể hiện một lưu ý chung. + subject: Thông qua quy hoạch sơ bộ + description: Loại này được phân cấp theo cấp bậc cha của các loại "Clash" và "Request", do đó đại diện cho một ghi chú chung. item_4: subject: Lập kế hoạch thiết kế - description: Kiểu này về mặt phân cấp là kiểu cha của kiểu "Clash" và "Request", do đó thể hiện một lưu ý chung. + description: Loại này được phân cấp theo cấp bậc cha của các loại "Clash" và "Request", do đó đại diện cho một ghi chú chung. children: item_0: - subject: Hoàn thành thiết kế + subject: Hoàn thiện thiết kế description: |- ## Mục tiêu - * Thiết kế hoàn thành - * Tất cả các bên đều hài lòng với kết quả của giai đoạn lập kế hoạch thiết kế + * Thiết kế đã xong + * Tất cả các bên đều hài lòng với kết quả của giai đoạn lập kế hoạch thiết kế ## Mô tả - * Thiết kế của dự án sẽ được hoàn thành - * Tất cả các bên đồng ý với thiết kế - * Chủ sở hữu hài lòng với kết quả - * ... + * Thiết kế của dự án sẽ hoàn thành + * Các bên thống nhất về thiết kế + * Chủ sở hữu hài lòng với kết quả + * ... item_1: subject: Đóng băng thiết kế - description: Kiểu này về mặt phân cấp là kiểu cha của kiểu "Clash" và "Request", do đó thể hiện một lưu ý chung. + description: Loại này được phân cấp theo cấp bậc cha của các loại "Clash" và "Request", do đó đại diện cho một ghi chú chung. item_5: subject: Giai đoạn xây dựng children: @@ -311,119 +311,119 @@ vi: description: |- ## Mục tiêu - * Lễ khởi công - * Thiết lập công trường xây dựng - * ... + * Lễ động thổ + * Bố trí địa điểm xây dựng + * ... ## Mô tả - * Chuẩn bị khu vực cho dự án - * Tập hợp đội ngũ - * ... + * Chuẩn bị mặt bằng cho dự án + * Tập hợp nhóm lại + * ... item_1: - subject: Đặt nền móng + subject: Nền móng description: |- ## Mục tiêu - * Đặt viên đá nền móng - * ... + *Đắp đá móng + * ... ## Mô tả - * Thiết lập máy trộn bê tông - * Thiết lập chuỗi cung ứng bê tông - * ... + * Lắp đặt máy trộn bê tông + * Thiết lập chuỗi cung ứng bê tông + * ... item_2: subject: Xây dựng công trình description: |- ## Mục tiêu - * Lễ cất nóc - * Tường và trần hoàn thành - * ... + * Lễ cất nóc + * Tường và trần đã hoàn thiện + * ... ## Mô tả - * Tạo tất cả các cấp cấu trúc của tòa nhà - * Lắp đặt cửa và cửa sổ - * Hoàn thiện cấu trúc mái - * ... + * Tạo tất cả các cấp độ kết cấu của tòa nhà + * Lắp đặt cửa ra vào và cửa sổ + * Hoàn thiện kết cấu mái + * ... item_3: subject: Hoàn thiện mặt tiền description: |- ## Mục tiêu - * Mặt tiền hoàn thành - * Toàn bộ tòa nhà đã chống thấm - * ... + * Mặt tiền đã hoàn thiện + * Toàn bộ tòa nhà không thấm nước + * ... ## Mô tả - * Lắp đặt tất cả các yếu tố của mặt tiền - * Hoàn thiện mái nhà - * ... + * Cài đặt tất cả các yếu tố cho mặt tiền + * Hoàn thiện mái nhà + * ... item_4: subject: Lắp đặt hệ thống dịch vụ tòa nhà description: |- ## Mục tiêu - * Tất cả các hệ thống dịch vụ tòa nhà đã sẵn sàng để sử dụng + * Tất cả các hệ thống dịch vụ tòa nhà đã sẵn sàng để sử dụng ## Mô tả - * Lắp đặt hệ thống sưởi - * Lắp đặt hệ thống điều hòa không khí - * Lắp đặt điện - * ... + * Lắp đặt hệ thống sưởi + * Lắp đặt hệ thống điều hòa + * Lắp đặt điện + * ... item_5: - subject: Hoàn thiện + subject: Những bước cuối cùng description: |- ## Mục tiêu - * Bàn giao chìa khóa - * Khách hàng hài lòng với tòa nhà của mình - * ... + * Bàn giao chìa khóa + * Khách hàng hài lòng với tòa nhà của mình + * ... ## Mô tả - * Hoàn tất lắp đặt hệ thống dịch vụ tòa nhà - * Hoàn thiện xây dựng nội thất - * Hoàn thiện mặt tiền - * ... + * Hoàn thiện lắp đặt hệ thống dịch vụ tòa nhà + * Hoàn thiện thi công nội thất + * Hoàn thiện mặt tiền + * ... item_6: - subject: Tiệc khai trương + subject: Tiệc tân gia description: |- ## Mục tiêu - * Có một bữa tiệc vui vẻ! + * Chúc bạn vui vẻ! ## Mô tả - * Mời đội ngũ xây dựng - * Mời bạn bè của bạn - * Mang theo một số đồ uống, món ăn nhẹ và nụ cười của bạn + * Mời đội xây dựng + * Mời bạn bè của bạn + * Mang theo một ít đồ uống, đồ ăn nhẹ và nụ cười của bạn demo-bim-project: name: "(Demo) Dự án BIM" - status_explanation: Tất cả các nhiệm vụ và tiểu dự án đang theo đúng tiến độ. Những người tham gia biết nhiệm vụ của họ. Hệ thống đã được thiết lập hoàn toàn. - description: Đây là tóm tắt ngắn gọn về mục tiêu của dự án BIM demo này. + status_explanation: Tất cả các nhiệm vụ và tiểu dự án đều được thực hiện đúng tiến độ. Những người liên quan biết nhiệm vụ của họ. Hệ thống đã được thiết lập hoàn chỉnh. + description: Đây là bản tóm tắt ngắn gọn về các mục tiêu của dự án BIM demo này. news: item_0: title: Chào mừng đến với dự án mẫu của bạn summary: | Chúng tôi rất vui vì bạn đã tham gia. - Trong module này, bạn có thể truyền đạt tin tức dự án đến các thành viên trong nhóm của bạn. + Trong mô-đun này, bạn có thể truyền đạt tin tức dự án cho các thành viên trong nhóm của mình. description: Tin tức thực tế categories: - item_0: Danh mục 1 (cần thay đổi trong cài đặt Dự án) + item_0: Loại 1 (sẽ được thay đổi trong cài đặt Dự án) queries: item_0: - name: Kế hoạch dự án + name: kế hoạch dự án item_1: - name: Các mốc thời gian + name: Các cột mốc quan trọng item_2: name: Nhiệm vụ item_3: - name: Lịch trình nhóm + name: Người lập kế hoạch nhóm project-overview: widgets: item_0: @@ -453,13 +453,13 @@ vi: Xin hãy cho chúng tôi biết nếu bạn có bất kỳ câu hỏi nào hoặc cần hỗ trợ. Liên hệ với chúng tôi: [support\[at\]openproject.com](mailto:support@openproject.com). item_4: options: - name: Thành viên + name: thành viên item_5: options: name: Work Packages item_6: options: - name: Các mốc thời gian + name: Các cột mốc quan trọng work_packages: item_0: subject: Khởi động dự án tạo mô hình BIM @@ -469,237 +469,237 @@ vi: Bước tiếp theo có thể là kiểm tra lịch trình và điều chỉnh các cuộc hẹn, bằng cách xem [Biểu đồ Gantt]({{opSetting:base_url}}/projects/demo-bim-project/work_packages?query_props=%7B%22c%22%3A%5B%22id%22%2C%22subject%22%2C%22startDate%22%2C%22dueDate%22%5D%2C%22tv%22%3Atrue%2C%22tzl%22%3A%22weeks%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22status%22%2C%22o%22%3A%22o%22%2C%22v%22%3A%5B%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%7D). item_1: subject: Chuẩn bị dự án - description: Kiểu này về mặt phân cấp là kiểu cha của kiểu "Clash" và "Request", do đó thể hiện một lưu ý chung. + description: Loại này được phân cấp theo cấp bậc cha của các loại "Clash" và "Request", do đó đại diện cho một ghi chú chung. children: item_0: subject: Thu thập dữ liệu và thông tin cụ thể của dự án cho mô hình BIM description: |- ## Mục tiêu - * Xác định chiến lược thông tin cho khách hàng (ví dụ: bằng cách sử dụng các câu hỏi bằng ngôn ngữ đơn giản) - * Nếu có, phân tích yêu cầu thông tin của khách hàng cho mô hình BIM - * Xác định chiến lược cung cấp thông tin phù hợp với nhu cầu của khách hàng + * Xác định chiến lược thông tin cho khách hàng (ví dụ: bằng cách sử dụng các câu hỏi bằng ngôn ngữ đơn giản) + * Nếu được cung cấp, hãy phân tích yêu cầu thông tin khách hàng đối với mô hình BIM + * Xác định chiến lược cung cấp thông tin theo nhu cầu của khách hàng ## Mô tả - * Phân tích nhu cầu và mục tiêu của khách hàng khi sử dụng phương pháp BIM - * Kết quả của nhiệm vụ này nên là: - * Các yêu cầu cho dự án - * Một chiến lược cho giai đoạn cung cấp - * ... + * Phân tích nhu cầu và mục tiêu của khách hàng khi sử dụng phương pháp BIM + * Kết quả của nhiệm vụ này là: + * Yêu cầu của dự án + * Chiến lược cho giai đoạn giao hàng + * ... item_1: subject: Tạo kế hoạch thực hiện BIM description: |- - # Mục tiêu + # mục tiêu - * Một kế hoạch thực hiện BIM sẽ được xác định theo các yêu cầu trao đổi (ERS) - * Tất cả các thành viên trong nhóm và các đối tác có một kế hoạch về cách đạt được từng mục tiêu của dự án + * Kế hoạch thực hiện BIM sẽ được xác định theo thông số kỹ thuật yêu cầu trao đổi (ERS) + * Tất cả các thành viên trong nhóm và đối tác đều có kế hoạch về cách đạt được từng mục tiêu của dự án # Mô tả - * Tùy thuộc vào các trường hợp sử dụng đã xác định, các Hướng dẫn Cung cấp Thông tin cá nhân sẽ được xác định - * Để xử lý các giao diện công nghệ, một phần mềm sẽ được xác định và phân tích, và xác minh - * ... + * Tùy thuộc vào các trường hợp sử dụng được xác định, Sổ tay hướng dẫn cung cấp thông tin riêng lẻ sẽ được xác định + * Để xử lý các giao diện công nghệ, cấu trúc liên kết phần mềm sẽ được xác định, phân tích và xác minh + * ... item_2: subject: Hoàn thành kế hoạch thực hiện BIM - description: Kiểu này về mặt phân cấp là kiểu cha của kiểu "Clash" và "Request", do đó thể hiện một lưu ý chung. + description: Loại này được phân cấp theo cấp bậc cha của các loại "Clash" và "Request", do đó đại diện cho một ghi chú chung. item_2: subject: Kết thúc giai đoạn chuẩn bị - description: Kiểu này về mặt phân cấp là kiểu cha của kiểu "Clash" và "Request", do đó thể hiện một lưu ý chung. + description: Loại này được phân cấp theo cấp bậc cha của các loại "Clash" và "Request", do đó đại diện cho một ghi chú chung. item_3: subject: Tạo mô hình BIM ban đầu - description: Kiểu này về mặt phân cấp là kiểu cha của kiểu "Clash" và "Request", do đó thể hiện một lưu ý chung. + description: Loại này được phân cấp theo cấp bậc cha của các loại "Clash" và "Request", do đó đại diện cho một ghi chú chung. children: item_0: - subject: Mô hình hóa mô hình BIM ban đầu + subject: Lập mô hình mô hình BIM ban đầu description: |- - # Mục tiêu + # mục tiêu - * Mô hình hóa mô hình BIM ban đầu - * Tạo một mô hình BIM cho toàn bộ nhóm dự án + * Lập mô hình mô hình BIM ban đầu + * Tạo mô hình BIM cho toàn bộ nhóm dự án # Mô tả - * Theo dữ liệu đã thu thập từ khách hàng, mô hình ban đầu sẽ được mô hình hóa - * Mô hình sẽ được mô hình hóa theo Ma trận LOD và chứa các thông tin cần thiết - * ... + * Theo dữ liệu thu thập được từ khách hàng, mô hình ban đầu sẽ được mô hình hóa + * Mô hình phải được mô hình hóa theo Ma trận LOD và chứa thông tin cần thiết + * ... item_1: - subject: Kiểm tra nội bộ mô hình ban đầu và sửa đổi + subject: Kiểm tra và sửa đổi mô hình ban đầu, nội bộ description: |- - # Mục tiêu + # mục tiêu - * Gửi mô hình BIM theo các tiêu chuẩn đã xác định + * Đệ trình mô hình BIM theo tiêu chuẩn đã xác định # Mô tả - * Mô hình sẽ được kiểm tra theo các tiêu chuẩn đã xác định (quy ước, LOD, ...) và sửa đổi - * ... + * Mô hình sẽ được kiểm tra, tuân theo các tiêu chuẩn đã xác định (quy ước, LOD, ...) và sửa đổi + * ... item_2: subject: Gửi mô hình BIM ban đầu - description: Kiểu này về mặt phân cấp là kiểu cha của kiểu "Clash" và "Request", do đó thể hiện một lưu ý chung. + description: Loại này được phân cấp theo cấp bậc cha của các loại "Clash" và "Request", do đó đại diện cho một ghi chú chung. item_4: - subject: Mô hình hóa, chu kỳ đầu tiên - description: Kiểu này về mặt phân cấp là kiểu cha của kiểu "Clash" và "Request", do đó thể hiện một lưu ý chung. + subject: Lập mô hình, chu kỳ đầu tiên + description: Loại này được phân cấp theo cấp bậc cha của các loại "Clash" và "Request", do đó đại diện cho một ghi chú chung. children: item_0: - subject: Tham chiếu các mô hình BIM bên ngoài + subject: Tham khảo các mô hình BIM bên ngoài description: |- - # Mục tiêu + # mục tiêu - * Có cơ sở để phát triển mô hình nội bộ / cung cấp câu trả lời - * Sử dụng mô hình bên ngoài để phát triển mô hình nội bộ + * Có nền tảng phát triển mô hình nội bộ/ đưa ra câu trả lời + * Sử dụng mô hình bên ngoài để phát triển mô hình bên trong # Mô tả - * Mô hình bên ngoài sẽ được tham chiếu trên nền tảng BIM, do đó được sử dụng để mô hình hóa mô hình nội bộ - * ... + * Mô hình bên ngoài sẽ được tham chiếu trong nền tảng BIM, do đó được sử dụng để mô hình hóa mô hình bên trong + * ... item_1: - subject: Mô hình hóa mô hình BIM + subject: Lập mô hình mô hình BIM description: |- - # Mục tiêu + # mục tiêu - * Tạo một mô hình BIM cho dự án - * Tạo một mô hình BIM cho toàn bộ nhóm dự án + * Tạo mô hình BIM cho dự án + * Tạo mô hình BIM cho toàn bộ nhóm dự án # Mô tả - * Mô hình sẽ được tạo ra theo kế hoạch thực hiện BIM - * ... + * Mô hình sẽ được tạo theo kế hoạch thực hiện BIM + * ... item_2: - subject: Chu kỳ đầu tiên, kiểm tra mô hình nội bộ và sửa đổi + subject: Chu kỳ đầu tiên, kiểm tra và sửa đổi mô hình nội bộ description: |- - # Mục tiêu + # mục tiêu - * Nộp mô hình BIM theo các tiêu chuẩn đã xác định + * Đệ trình mô hình BIM theo tiêu chuẩn đã xác định # Mô tả - * Mô hình sẽ được kiểm tra theo các tiêu chuẩn đã xác định (quy ước, LOD, ...) và sửa đổi. - * ... + * Mô hình sẽ được kiểm tra, theo các tiêu chuẩn đã xác định (quy ước, LOD, ...) và sửa đổi. + * ... item_3: - subject: Nộp mô hình BIM - description: Kiểu này về mặt phân cấp là kiểu cha của kiểu "Clash" và "Request", do đó thể hiện một lưu ý chung. + subject: Đệ trình mô hình BIM + description: Loại này được phân cấp theo cấp bậc cha của các loại "Clash" và "Request", do đó đại diện cho một ghi chú chung. item_5: subject: Phối hợp, chu kỳ đầu tiên - description: Kiểu này về mặt phân cấp là kiểu cha của kiểu "Clash" và "Request", do đó thể hiện một lưu ý chung. + description: Loại này được phân cấp theo cấp bậc cha của các loại "Clash" và "Request", do đó đại diện cho một ghi chú chung. children: item_0: subject: Phối hợp các mô hình BIM khác nhau description: |- - # Mục tiêu + # mục tiêu - * Tập hợp các mô hình BIM của toàn bộ nhóm dự án - * Phối hợp các vấn đề được xác định + * Lắp ráp các mô hình BIM khác nhau của toàn bộ nhóm dự án + * Phối hợp các vấn đề được xác định # Mô tả - * Các mô hình BIM khác nhau sẽ được tập hợp và kiểm tra - * Các vấn đề cụ thể của mô hình được xác định sẽ được truyền đạt qua các tệp BCF - * ... + * Các mô hình BIM khác nhau sẽ được lắp ráp và kiểm tra + * Các vấn đề cụ thể của mô hình được xác định sẽ được thông báo qua tệp BCF + * ... item_1: subject: Quản lý vấn đề, chu kỳ đầu tiên item_2: - subject: Hoàn thành phối hợp, chu kỳ đầu tiên - description: Kiểu này về mặt phân cấp là kiểu cha của kiểu "Clash" và "Request", do đó thể hiện một lưu ý chung. + subject: Kết thúc phối hợp, chu kỳ đầu tiên + description: Loại này được phân cấp theo cấp bậc cha của các loại "Clash" và "Request", do đó đại diện cho một ghi chú chung. item_6: - subject: Mô hình hóa & phối hợp, chu kỳ thứ hai - description: "## Mục tiêu\r\n\r\n* ...\r\n\r\n## Mô tả\r\n\r\n* \\ ..." + subject: Lập mô hình & điều phối, chu kỳ thứ hai + description: "## Mục tiêu\r\n\r\n* ...\r\n\r\n## Mô tả\r\n\r\n* \\ ..." item_7: - subject: Mô hình hóa & phối hợp, ... chu kỳ - description: Kiểu này về mặt phân cấp là kiểu cha của kiểu "Clash" và "Request", do đó thể hiện một lưu ý chung. + subject: Lập mô hình & điều phối, ... chu trình + description: Loại này được phân cấp theo cấp bậc cha của các loại "Clash" và "Request", do đó đại diện cho một ghi chú chung. item_8: - subject: Mô hình hóa & phối hợp, (n-th trừ 1) chu kỳ - description: "## Mục tiêu\r\n\r\n* ...\r\n\r\n## Mô tả\r\n\r\n* \\ ..." + subject: Lập mô hình & điều phối, chu trình (n-th trừ 1) + description: "## Mục tiêu\r\n\r\n* ...\r\n\r\n## Mô tả\r\n\r\n* \\ ..." item_9: - subject: Mô hình hóa & phối hợp chu kỳ n-th - description: Kiểu này về mặt phân cấp là kiểu cha của kiểu "Clash" và "Request", do đó thể hiện một lưu ý chung. + subject: Lập mô hình và điều phối chu kỳ thứ n + description: Loại này được phân cấp theo cấp bậc cha của các loại "Clash" và "Request", do đó đại diện cho một ghi chú chung. item_10: - subject: Hoàn thành mô hình hóa & phối hợp, chu kỳ n-th - description: Kiểu này về mặt phân cấp là kiểu cha của kiểu "Clash" và "Request", do đó thể hiện một lưu ý chung. + subject: Hoàn thiện mô hình hóa và điều phối, chu kỳ thứ n + description: Loại này được phân cấp theo cấp bậc cha của các loại "Clash" và "Request", do đó đại diện cho một ghi chú chung. item_11: subject: Sử dụng mô hình cho giai đoạn xây dựng children: item_0: - subject: Bàn giao mô hình cho đội xây dựng + subject: Mô hình bàn giao cho đội thi công description: |- ## Mục tiêu - * Mọi người đều biết mô hình và nhiệm vụ của họ - * Mọi người đều nhận được tất cả thông tin liên quan, dựa trên mô hình - * ... + * Mọi người đều biết mô hình và nhiệm vụ của mình + * Mọi người đều nhận được tất cả thông tin liên quan, dựa trên mô hình + * ... ## Mô tả - * Khởi động tại công trường bao gồm giới thiệu mô hình - * Tất cả các đối tượng nên có thông tin cần thiết cho các nhiệm vụ được phân công. Nếu không, cần làm giàu dữ liệu của mô hình - * ... + * Lễ khai mạc tại công trường bao gồm phần giới thiệu về mô hình + * Tất cả các đối tượng cần có thông tin cần thiết cho nhiệm vụ được giao. Nếu không, cần phải làm giàu dữ liệu của mô hình + * ... item_1: subject: Xây dựng tòa nhà description: |- ## Mục tiêu - * Các vấn đề mới phát hiện trên công trường sẽ được xử lý dựa trên mô hình - * Các vấn đề sẽ được tài liệu hóa bằng cách sử dụng các tệp BCF và mô hình BIM + * Các vấn đề mới phát hiện trên công trường sẽ được xử lý dựa trên mô hình + * Các vấn đề sẽ được ghi lại bằng cách sử dụng tệp BCF và mô hình BIM ## Mô tả - * Các vấn đề mới sẽ được tài liệu hóa bằng các tệp BCF như ghi chú dán cho mô hình - * Các tệp BCF sẽ được sử dụng để phân công, theo dõi và sửa chữa các vấn đề - * ... + * Các vấn đề mới sẽ được ghi lại bằng tệp BCF làm ghi chú dán cho mô hình + * Các tệp BCF sẽ được sử dụng để chỉ định, theo dõi và khắc phục sự cố + * ... item_2: - subject: Hoàn thành xây dựng + subject: Hoàn thiện công trình item_12: subject: Quản lý vấn đề, giai đoạn xây dựng item_13: - subject: Bàn giao cho Quản lý Cơ sở + subject: Bàn giao cho quản lý cơ sở description: |- ## Mục tiêu - * Mô hình BIM sẽ được sử dụng cho Quản lý Cơ sở - * Mô hình cung cấp tất cả thông tin liên quan cho việc đưa vào sử dụng và vận hành tòa nhà - * ... + * Mô hình BIM sẽ được sử dụng cho Quản lý cơ sở vật chất + * Mô hình cung cấp tất cả các thông tin liên quan cho việc vận hành và vận hành tòa nhà + * ... ## Mô tả - * Mô hình chứa thông tin liên quan cho quản lý cơ sở - * Mô hình có thể được sử dụng cho hệ thống vận hành của tòa nhà - * ... + * Mô hình chứa thông tin liên quan cho người quản lý cơ sở + *Mô hình có thể sử dụng cho hệ điều hành của tòa nhà + * ... item_14: subject: Quản lý tài sản description: Tận hưởng tòa nhà của bạn :) demo-bcf-management-project: name: "(Demo) Quản lý BCF" - status_explanation: Tất cả các nhiệm vụ đang theo đúng tiến độ. Những người tham gia đều biết nhiệm vụ của mình. Hệ thống đã được thiết lập hoàn chỉnh. - description: Đây là một tóm tắt ngắn gọn về các mục tiêu của dự án quản lý BCF demo này. + status_explanation: Mọi công việc đều đúng tiến độ. Những người liên quan biết nhiệm vụ của họ. Hệ thống đã được thiết lập hoàn chỉnh. + description: Đây là bản tóm tắt ngắn gọn về các mục tiêu của dự án quản lý BCF demo này. ifc_models: item_0: name: Bệnh viện - Kiến trúc (cc-by-sa-3.0 Autodesk Inc.) item_1: name: Bệnh viện - Kết cấu (cc-by-sa-3.0 Autodesk Inc.) item_2: - name: Bệnh viện - Cơ điện (cc-by-sa-3.0 Autodesk Inc.) + name: Bệnh viện - Cơ khí (cc-by-sa-3.0 Autodesk Inc.) categories: - item_0: Danh mục 1 (cần thay đổi trong Cài đặt Dự án) + item_0: Loại 1 (sẽ được thay đổi trong cài đặt Dự án) queries: item_0: - name: Vấn đề + name: vấn đề item_1: - name: Va chạm + name: Đụng độ item_2: - name: Yêu cầu + name: yêu cầu item_3: - name: Ghi chú + name: Bình luận item_4: - name: Kế hoạch dự án + name: kế hoạch dự án item_5: - name: Cột mốc + name: Các cột mốc quan trọng item_6: name: Nhiệm vụ item_7: - name: Lịch trình nhóm + name: Người lập kế hoạch nhóm boards: bcf: - name: Các vấn đề BCF + name: vấn đề BCF project-overview: widgets: item_0: @@ -728,7 +728,7 @@ vi: Xin vui lòng cho chúng tôi biết nếu bạn có bất kỳ câu hỏi nào hoặc cần hỗ trợ. Liên hệ với chúng tôi: [support\[at\]openproject.com](mailto:support@openproject.com). item_4: options: - name: Thành viên + name: thành viên item_5: options: - name: Work Packages + name: Gói công việc diff --git a/modules/bim/config/locales/crowdin/vi.yml b/modules/bim/config/locales/crowdin/vi.yml index 93b702f15d4..0a4467660bd 100644 --- a/modules/bim/config/locales/crowdin/vi.yml +++ b/modules/bim/config/locales/crowdin/vi.yml @@ -1,15 +1,15 @@ #English strings go here for Rails i18n vi: plugin_openproject_bim: - name: "Tính năng BIM và BCF của OpenProject" - description: "Plugin OpenProject này giới thiệu các tính năng BIM và BCF." + name: "Chức năng OpenProject BIM và BCF" + description: "Plugin OpenProject này giới thiệu chức năng BIM và BCF." bim: label_bim: 'BIM' bcf: label_bcf: 'BCF' label_imported_failed: 'Nhập các chủ đề BCF thất bại' label_imported_successfully: 'Đã nhập các chủ đề BCF thành công' - label_bcf_issue_associated: "BCF issue associated" + label_bcf_issue_associated: "Vấn đề BCF liên quan" issues: "Các vấn đề" recommended: 'khuyến nghị' not_recommended: 'không khuyến nghị' @@ -19,15 +19,15 @@ vi: file_invalid: "Tệp BCF không hợp lệ" x_bcf_issues: zero: 'Không có vấn đề BCF' - one: 'Một vấn đề BCF' - other: '%{count} vấn đề trong BCF' + one: 'Một vấn đề về BCF' + other: '%{count} vấn đề về BCF' bcf_xml: xml_file: 'Tệp BCF XML' - import_title: 'Nhập khẩu' - export: 'Xuất' + import_title: 'nhập khẩu' + export: 'xuất khẩu' import_update_comment: '(Cập nhật từ file BCF)' import_failed: 'Không thể nhập file BCF: %{error}' - import_failed_unsupported_bcf_version: 'Không thể đọc tệp BCF: Phiên bản BCF không được hỗ trợ. Vui lòng đảm bảo phiên bản ít nhất là %{minimal_version} hoặc cao hơn.' + import_failed_unsupported_bcf_version: 'Không đọc được tệp BCF: Phiên bản BCF không được hỗ trợ. Hãy đảm bảo phiên bản ít nhất %{minimal_version} trở lên.' import_successful: 'Đã nhập %{count} vấn đề' import_canceled: 'Nhập BCF-XML bị hủy.' type_not_active: "Loại vấn đề không được kích hoạt cho dự án này." @@ -71,7 +71,7 @@ vi: project_module_bim: "BCF" permission_view_linked_issues: "Xem các vấn đề trong BCF" permission_manage_bcf: "Nhập và quản lý các vấn đề trong BCF" - permission_delete_bcf: "Xóa các vấn đề BCF" + permission_delete_bcf: "Xóa các vấn đề về BCF" oauth: scopes: bcf_v2_1: "Truy cập đầy đủ vào API BCF v2.1" @@ -81,15 +81,15 @@ vi: bim/ifc_models/ifc_model: "Mô hình IFC" attributes: bim/ifc_models/ifc_model: - ifc_attachment: "Tệp IFC" + ifc_attachment: "tập tin IFC" is_default: "Mô hình mặc định" - attachments: "Tệp IFC" + attachments: "tập tin IFC" bim/bcf/issue: - bcf_comment: "BCF comment" - bim_snippet: "BIM snippet" - reference_links: "Reference links" - stage: "Stage" - viewpoint: "Góc nhìn" + bcf_comment: "Bình luận BCF" + bim_snippet: "đoạn trích BIM" + reference_links: "Liên kết tham khảo" + stage: "sân khấu" + viewpoint: "quan điểm" errors: models: bim/ifc_models/ifc_model: @@ -98,20 +98,20 @@ vi: ifc_attachment_missing: "Không có tập tin ifc đính kèm." invalid_ifc_file: "Tệp được cung cấp không phải là tệp IFC hợp lệ." bim/bcf/viewpoint: - bitmaps_not_writable: "hình ảnh không thể ghi được vì nó chưa được thực hiện." - index_not_integer: "chỉ số(index) không phải là một số nguyên." + bitmaps_not_writable: "bitmap không thể ghi được vì nó chưa được triển khai." + index_not_integer: "chỉ mục không phải là số nguyên." invalid_clipping_planes: "clipping_planes không hợp lệ" - invalid_components: "các thành phần không hợp lệ" + invalid_components: "thành phần không hợp lệ." invalid_lines: "dòng không hợp lệ." - invalid_orthogonal_camera: "orthogonal_camera không hợp lệ" - invalid_perspective_camera: "perspective_camera không hợp lệ" + invalid_orthogonal_camera: "orthogonal_Camera không hợp lệ." + invalid_perspective_camera: "phối cảnh_máy ảnh không hợp lệ." mismatching_guid: "Hướng dẫn trong json_viewpoint không khớp với hướng dẫn của mô hình." - no_json: "Không phải là một json có cấu trúc tốt." + no_json: "Đây không phải là một json có cấu trúc tốt." snapshot_type_unsupported: "snapshot_type cần phải là 'png' hoặc 'jpg'." - snapshot_data_blank: "snapshot_data không được để trống." + snapshot_data_blank: "snapshot_data cần được cung cấp." unsupported_key: "Một thuộc tính json không được hỗ trợ được bao gồm." bim/bcf/issue: - uuid_already_taken: "Không thể nhập khẩu vấn đề BCF này vì đã có một vấn đề khác với GUID giống nhau. Có thể vấn đề BCF này đã được nhập khẩu vào một dự án khác?" + uuid_already_taken: "Không thể nhập vấn đề BCF này vì đã có một vấn đề khác có cùng GUID. Có thể vấn đề BCF này đã được nhập vào một dự án khác không?" ifc_models: label_ifc_models: 'Mô hình IFC' label_new_ifc_model: 'Mô hình IFC mới' @@ -124,18 +124,18 @@ vi: check_2: 'Kiểm tra xem ít nhất một mô hình IFC được đặt thành "Mặc định".' no_results: "Không có mô hình IFC đã được tải lên trong dự án này." conversion_status: - label: 'Đang xử lý…' - pending: 'Đang chờ' - processing: 'Đang xử lý' - completed: 'Đã hoàn tất' - error: 'Lỗi' + label: 'Xử lý?' + pending: 'đang chờ xử lý' + processing: 'chế biến' + completed: 'Đã hoàn thành' + error: 'lỗi' processing_notice: processing_default: 'Các mô hình IFC mặc định sau đây vẫn đang được xử lý và do đó không có sẵn:' flash_messages: upload_successful: 'Tải lên thành công. Bây giờ nó sẽ được xử lý và sẽ sẵn sàng để sử dụng trong vài phút nữa.' conversion: missing_commands: "Các lệnh chuyển đổi IFC sau bị thiếu trên hệ thống này: %{names}" - project_module_ifc_models: "Mô hình IFC" + project_module_ifc_models: "mô hình IFC" permission_view_ifc_models: "Xem các mô hình IFC" permission_manage_ifc_models: "Nhập và quản lý các mô hình IFC" extraction: diff --git a/modules/boards/config/locales/crowdin/js-vi.yml b/modules/boards/config/locales/crowdin/js-vi.yml index 05e296f0aea..00495ffb340 100644 --- a/modules/boards/config/locales/crowdin/js-vi.yml +++ b/modules/boards/config/locales/crowdin/js-vi.yml @@ -3,7 +3,7 @@ vi: ee: upsell: board_view: - description: 'Bạn có muốn tự động hóa quy trình làm việc của mình với các bảng không? Các bảng nâng cao là một tiện ích bổ sung Enterprise. Vui lòng nâng cấp lên gói trả phí.' + description: 'Bạn có muốn tự động hóa quy trình công việc của mình với Bảng không? Bảng nâng cao là một tiện ích bổ sung dành cho Doanh nghiệp. Vui lòng nâng cấp lên gói trả phí.' js: boards: create_new: 'Tạo bảng mới' @@ -16,40 +16,40 @@ vi: is_locked: 'Phiên bản đã bị khóa. Không thể thêm mục vào phiên bản này.' is_closed: 'Phiên bản đã đóng. Không thể thêm mục vào phiên bản này.' close_version: 'Đóng phiên bản' - open_version: 'Mở phiên bản' - lock_version: 'Khóa phiên bản' - unlock_version: 'Mở khóa phiên bản' + open_version: 'Phiên bản mở' + lock_version: 'Phiên bản khóa' + unlock_version: 'Phiên bản mở khóa' edit_version: 'Chỉnh sửa phiên bản' show_version: 'Hiển thị phiên bản' - locked: 'Đã khóa' - closed: 'Đã đóng' + locked: 'bị khóa' + closed: 'đóng cửa' new_board: 'Bảng mới' add_list: 'Thêm danh sách vào bảng' add_card: 'Thêm thẻ' - error_attribute_not_writable: "Không thể di chuyển gói công việc, %{attribute} không thể ghi." + error_attribute_not_writable: "Không thể di chuyển gói công việc, %{attribute} không thể ghi được." error_loading_the_list: "Lỗi khi tải danh sách: %{error_message}" error_permission_missing: "Thiếu quyền tạo truy vấn công khai" - error_cannot_move_into_self: "Bạn không thể di chuyển một gói công việc vào cột của chính nó." - text_hidden_list_warning: "Không phải tất cả các danh sách đều được hiển thị vì bạn thiếu quyền. Liên hệ với quản trị viên của bạn để biết thêm thông tin." + error_cannot_move_into_self: "Bạn không thể di chuyển gói công việc vào cột riêng của nó." + text_hidden_list_warning: "Không phải tất cả các danh sách đều được hiển thị vì bạn thiếu sự cho phép. Hãy liên hệ với quản trị viên của bạn để biết thêm thông tin." click_to_remove_list: "Nhấp để xóa danh sách" board_type: text: 'Loại bảng' - free: 'cơ bản' + free: 'Cơ bản' select_board_type: 'Vui lòng chọn loại bảng bạn cần.' free_text: > - Bắt đầu từ đầu với một bảng trắng. Thêm thẻ và cột vào bảng này một cách thủ công. - action: 'Bảng hành động' + Bắt đầu lại từ đầu với một bảng trống. Thêm thẻ và cột theo cách thủ công vào bảng này. + action: 'bảng hành động' action_by_attribute: 'Bảng hành động (%{attribute})' action_text: > - Bảng với các danh sách lọc theo thuộc tính %{attribute}. Di chuyển các gói công việc vào các danh sách khác sẽ cập nhật thuộc tính của chúng. + Một bảng có các danh sách được lọc trên thuộc tính %{attribute}. Việc di chuyển các gói công việc sang danh sách khác sẽ cập nhật thuộc tính của chúng. action_text_subprojects: > - Bảng với các cột tự động cho các dự án con. Kéo các gói công việc vào các danh sách khác sẽ cập nhật dự án (con) tương ứng. + Bảng có các cột tự động cho các tiểu dự án. Kéo các gói công việc vào danh sách khác sẽ cập nhật dự án (phụ) tương ứng. action_text_subtasks: > - Bảng với các cột tự động cho các phần tử phụ. Kéo các gói công việc vào các danh sách khác sẽ cập nhật phần tử cha tương ứng. + Bảng có các cột tự động dành cho các phần tử phụ. Kéo các gói công việc vào danh sách khác sẽ cập nhật gói gốc tương ứng. action_text_status: > - Bảng kiểu kanban cơ bản với các cột cho trạng thái như To Do, In Progress, Done. + Bảng kiểu Kanban cơ bản với các cột cho trạng thái như Việc cần làm, Đang tiến hành, Đã xong. action_text_assignee: > - Bảng với các cột tự động dựa trên người dùng được phân công. Lý tưởng cho việc phân phối gói công việc. + Bảng với các cột tự động dựa trên người dùng được chỉ định. Lý tưởng cho việc gửi các gói công việc. action_text_version: > Bảng với các cột tự động dựa trên thuộc tính phiên bản. Lý tưởng cho việc lập kế hoạch phát triển sản phẩm. action_type: @@ -57,28 +57,28 @@ vi: status: Trạng thái version: Phiên bản subproject: dự án con - subtasks: cha-con + subtasks: cha mẹ-con cái board_type_title: - assignee: Người được giao - status: Trạng thái + assignee: Người được chuyển nhượng + status: trạng thái version: Phiên bản - subproject: Dự án con + subproject: dự án con subtasks: Cha-con basic: Cơ bản select_attribute: "Thuộc tính hành động" add_list_modal: labels: - assignee: Chọn người dùng để thêm vào danh sách người được phân công mới + assignee: Chọn người dùng để thêm làm danh sách người được chuyển nhượng mới status: Chọn trạng thái để thêm vào danh sách mới version: Chọn phiên bản để thêm vào danh sách mới - subproject: Chọn dự án con để thêm vào danh sách mới - subtasks: Chọn gói công việc để thêm vào danh sách mới + subproject: Chọn tiểu dự án để thêm làm danh sách mới + subtasks: Chọn gói công việc để thêm làm danh sách mới warning: status: | - Hiện tại không có trạng thái nào có sẵn.
    - Hoặc là không có hoặc tất cả đã được thêm vào bảng. + Hiện tại không có trạng thái nào.
    + Hoặc không có hoặc tất cả chúng đều đã được thêm vào bảng. assignee: Không có thành viên nào khớp với giá trị bộ lọc của bạn.
    - no_member: Dự án này hiện không có thành viên nào có thể thêm vào.
    + no_member: Dự án này hiện không có bất kỳ thành viên nào có thể được thêm vào.
    add_members: Thêm thành viên mới vào dự án này để chọn lại người dùng. configuration_modal: title: 'Cấu hình bảng này' diff --git a/modules/boards/config/locales/crowdin/vi.yml b/modules/boards/config/locales/crowdin/vi.yml index d2be9022e22..96189e05d50 100644 --- a/modules/boards/config/locales/crowdin/vi.yml +++ b/modules/boards/config/locales/crowdin/vi.yml @@ -1,40 +1,40 @@ #English strings go here vi: plugin_openproject_boards: - name: "Bảng OpenProject" - description: "Cung cấp các chế độ xem bảng." + name: "Ban dự án mở" + description: "Cung cấp chế độ xem bảng." permission_show_board_views: "Xem bảng" permission_manage_board_views: "Quản lý bảng" - project_module_board_view: "Các bảng" + project_module_board_view: "bảng" ee: upsell: board_view: - description: 'Bạn có muốn tự động hóa quy trình làm việc của mình với các bảng không? Các bảng nâng cao là một tiện ích bổ sung Enterprise. Vui lòng nâng cấp lên gói trả phí.' + description: 'Bạn có muốn tự động hóa quy trình công việc của mình với Bảng không? Bảng nâng cao là một tiện ích bổ sung dành cho Doanh nghiệp. Vui lòng nâng cấp lên gói trả phí.' boards: - label_board: "Bảng" - label_boards: "Các bảng" + label_board: "bảng" + label_boards: "bảng" label_create_new_board: "Tạo bảng mới" label_board_type: "Loại bảng" board_types: free: Cơ bản action: "Bảng hành động (%{attribute})" board_type_attributes: - assignee: Người được giao - status: Trạng thái + assignee: Người được chuyển nhượng + status: trạng thái version: Phiên bản - subproject: Dự án con + subproject: dự án con subtasks: Cha-con basic: Cơ bản board_type_descriptions: basic: > - Bắt đầu từ đầu với một bảng trắng. Thêm thẻ và cột vào bảng này một cách thủ công. + Bắt đầu lại từ đầu với một bảng trống. Thêm thẻ và cột theo cách thủ công vào bảng này. status: > - Bảng kiểu kanban cơ bản với các cột cho trạng thái như To Do, In Progress, Done. + Bảng kiểu Kanban cơ bản với các cột cho trạng thái như Việc cần làm, Đang tiến hành, Đã xong. assignee: > - Bảng với các cột tự động dựa trên người dùng được phân công. Lý tưởng cho việc phân phối gói công việc. + Bảng với các cột tự động dựa trên người dùng được chỉ định. Lý tưởng cho việc gửi các gói công việc. version: > Bảng với các cột tự động dựa trên thuộc tính phiên bản. Lý tưởng cho việc lập kế hoạch phát triển sản phẩm. subproject: > - Bảng với các cột tự động cho các dự án con. Kéo các gói công việc vào các danh sách khác sẽ cập nhật dự án (con) tương ứng. + Bảng có các cột tự động cho các tiểu dự án. Kéo các gói công việc vào danh sách khác sẽ cập nhật dự án (phụ) tương ứng. subtasks: > - Bảng với các cột tự động cho các phần tử phụ. Kéo các gói công việc vào các danh sách khác sẽ cập nhật phần tử cha tương ứng. + Bảng có các cột tự động dành cho các phần tử phụ. Kéo các gói công việc vào danh sách khác sẽ cập nhật gói gốc tương ứng. diff --git a/modules/budgets/config/locales/crowdin/vi.yml b/modules/budgets/config/locales/crowdin/vi.yml index 4011c87f3d5..b9ae1cd482d 100644 --- a/modules/budgets/config/locales/crowdin/vi.yml +++ b/modules/budgets/config/locales/crowdin/vi.yml @@ -21,22 +21,22 @@ #++ vi: plugin_budgets_engine: - name: "Ngân sách" + name: "ngân sách" activerecord: attributes: budget: - author: "Tác giả" - available: "Khả dụng" - budget: "Có kế hoạch" - budget_ratio: "Đã chi (tỷ lệ)" - description: "Mô tả" - spent: "Đã chi" - status: "Trạng thái" + author: "tác giả" + available: "có sẵn" + budget: "Đã lên kế hoạch" + budget_ratio: "Đã chi tiêu (tỷ lệ)" + description: "mô tả" + spent: "chi tiêu" + status: "trạng thái" subject: "Chủ đề" type: "Loại chi phí" labor_budget: "Chi phí lao động dự kiến" material_budget: "Chi phí đơn vị dự kiến" - base_amount: "Tổng Ngân Sách" + base_amount: "Số tiền cơ bản" work_package: budget_subject: "Tiêu đề ngân sách" models: @@ -44,42 +44,42 @@ vi: material_budget_item: "Đơn vị" activity: filter: - budget: "Ngân sách" + budget: "ngân sách" attributes: budget: "Ngân sách" button_add_budget_item: "Thêm chi phí đã lên kế hoạch" button_add_budget: "Thêm ngân sách" button_add_cost_type: "Thêm kiểu chi phí" button_cancel_edit_budget: "Hủy chỉnh sửa ngân sách" - button_cancel_edit_costs: "Hủy chỉnh sửa chi phí" + button_cancel_edit_costs: "Hủy chi phí chỉnh sửa" caption_labor: "Lao động" caption_labor_costs: "Chi phí lao động thực tế" caption_material_costs: "Chi phí đơn vị thực tế" - budgets_title: "Ngân sách" + budgets_title: "ngân sách" events: - budget: "Ngân sách đã được chỉnh sửa" + budget: "Đã chỉnh sửa ngân sách" help_click_to_edit: "Nhấn vào đây để chỉnh sửa." - help_currency_format: "Định dạng giá trị tiền tệ hiển thị. %n sẽ được thay thế bằng giá trị tiền tệ, %u sẽ được thay thế bằng đơn vị tiền tệ." - help_override_rate: "Nhập giá trị ở đây để ghi đè tỷ lệ mặc định." + help_currency_format: "Định dạng của giá trị tiền tệ được hiển thị. %n được thay thế bằng giá trị tiền tệ, %u được thay thế bằng đơn vị tiền tệ." + help_override_rate: "Nhập một giá trị vào đây để ghi đè tỷ lệ mặc định." label_budget: "Ngân sách" - label_budget_available: "Ngân Sách sẵn sàng" + label_budget_available: "Ngân sách có sẵn" label_budget_id: "Ngân sách #%{id}" label_budget_new: "Ngân sách mới" - label_budget_planned: "Kế hoạch Ngân Sách" - label_budget_plural: "Ngân sách" - label_budget_spent: "Ngân Sách đã chi tiêu" - label_budget_spent_ratio: "Tỉ Lệ Ngân Sách đã chi tiêu" + label_budget_planned: "Ngân sách dự kiến" + label_budget_plural: "ngân sách" + label_budget_spent: "Ngân sách chi tiêu" + label_budget_spent_ratio: "Tỷ lệ chi ngân sách" label_deliverable: "Ngân sách" label_example_placeholder: "ví dụ: %{decimal}" label_view_all_budgets: "Xem tất cả ngân sách" label_yes: "Có" - label_budget_totals: "Tổng số" - label_budget_details: "Ngân Sách Chi tiết" + label_budget_totals: "tổng cộng" + label_budget_details: "Chi tiết ngân sách" notice_budget_conflict: "Các gói công việc phải thuộc cùng một dự án." - notice_no_budgets_available: "Không có ngân sách nào." + notice_no_budgets_available: "Không có sẵn ngân sách." permission_edit_budgets: "Chỉnh sửa ngân sách" permission_view_budgets: "Xem ngân sách" - project_module_budgets: "Ngân sách" - text_budget_reassign_to: "Gán lại chúng cho ngân sách này:" + project_module_budgets: "ngân sách" + text_budget_reassign_to: "Chỉ định lại chúng cho ngân sách này:" text_budget_delete: "Xóa ngân sách khỏi tất cả các gói công việc" - text_budget_destroy_assigned_wp: "Có %{count} gói công việc được gán cho ngân sách này. Bạn muốn làm gì?" + text_budget_destroy_assigned_wp: "Có %{count} gói công việc được giao cho ngân sách này. Bạn muốn làm gì?" diff --git a/modules/calendar/config/locales/crowdin/js-vi.yml b/modules/calendar/config/locales/crowdin/js-vi.yml index 2c5a7515b76..e9be0aa5410 100644 --- a/modules/calendar/config/locales/crowdin/js-vi.yml +++ b/modules/calendar/config/locales/crowdin/js-vi.yml @@ -3,7 +3,7 @@ vi: js: calendar: create_new: 'Tạo lịch mới' - title: 'Lịch' - too_many: 'Tổng cộng có %{count} công việc, nhưng chỉ %{max} có thể được hiển thị.' + title: 'lịch' + too_many: 'Tổng cộng có %{count} gói công việc nhưng chỉ có thể hiển thị %{max}.' unsaved_title: 'Lịch không tên' - label_calendar_plural: 'Các lịch' + label_calendar_plural: 'lịch' diff --git a/modules/calendar/config/locales/crowdin/vi.yml b/modules/calendar/config/locales/crowdin/vi.yml index b0c605fa96a..e6a0a02bf6a 100644 --- a/modules/calendar/config/locales/crowdin/vi.yml +++ b/modules/calendar/config/locales/crowdin/vi.yml @@ -1,12 +1,12 @@ #English strings go here vi: plugin_openproject_calendar: - name: "Lịch OpenProject" - description: "Cung cấp các chế độ xem lịch." + name: "Lịch dự án mở" + description: "Cung cấp chế độ xem lịch." label_calendar: "Lịch" - label_calendar_plural: "Lịch" + label_calendar_plural: "lịch" label_new_calendar: "Lịch mới" permission_view_calendar: "Xem lịch" permission_manage_calendars: "Quản lý lịch" permission_share_calendars: "Đăng ký iCalendars" - project_module_calendar_view: "Các lịch" + project_module_calendar_view: "lịch" diff --git a/modules/costs/config/locales/crowdin/js-vi.yml b/modules/costs/config/locales/crowdin/js-vi.yml index 01c211b7b0b..e7e657e54b9 100644 --- a/modules/costs/config/locales/crowdin/js-vi.yml +++ b/modules/costs/config/locales/crowdin/js-vi.yml @@ -23,13 +23,13 @@ vi: js: text_are_you_sure: "Bạn có chắc không?" myTimeTracking: - noSpecificTime: "No specific time" + noSpecificTime: "Không có thời gian cụ thể" work_packages: property_groups: - costs: "Chi phí" + costs: "chi phí" properties: overallCosts: "Chi phí tổng cộng" - spentUnits: "Các đơn vị đã chi" + spentUnits: "Đơn vị đã chi" button_log_costs: "Ghi lại chi phí đơn vị" label_hour: "giờ" - label_hours: "hours" + label_hours: "giờ" diff --git a/modules/costs/config/locales/crowdin/vi.yml b/modules/costs/config/locales/crowdin/vi.yml index ae9b47b1e9d..bcce2c886ab 100644 --- a/modules/costs/config/locales/crowdin/vi.yml +++ b/modules/costs/config/locales/crowdin/vi.yml @@ -22,18 +22,18 @@ vi: plugin_costs: name: "Thời gian và chi phí" - description: "Mô-đun này thêm các tính năng để lập kế hoạch và theo dõi chi phí của các dự án." + description: "Mô-đun này bổ sung thêm các tính năng lập kế hoạch và theo dõi chi phí của dự án." activerecord: attributes: cost_entry: work_package: "Work Package" overridden_costs: "Chi phí ghi đè" spent: "Đã chi" - spent_on: "Ngày" - logged_by: "Đã ghi bởi" - entity: Ghi lại cho - entity_id: Ghi lại cho - entity_gid: Ghi lại cho + spent_on: "ngày" + logged_by: "Đăng nhập bởi" + entity: Đã đăng nhập + entity_id: Đã đăng nhập + entity_gid: Đã đăng nhập cost_type: unit: "Tên đơn vị" unit_plural: "Tên đơn vị đa năng" @@ -50,181 +50,181 @@ vi: user: default_rates: "Tỷ lệ mặc định" time_entry: - project: Dự án - user: Người dùng + project: dự án + user: người dùng work_package: Work Package subject: Chủ đề hours: Giờ - comments: Nhận xét + comments: bình luận activity: Hoạt động spent_on: Ngày start_time: Thời gian bắt đầu - end_time: Thời gian hoàn thành - time: Thời gian - entity: Ghi lại cho - entity_id: Ghi lại cho - entity_gid: Ghi lại cho + end_time: Thời gian kết thúc + time: thời gian + entity: Đã đăng nhập + entity_id: Đã đăng nhập + entity_gid: Đã đăng nhập models: time_entry: - other: "Time entries" + other: "Mục thời gian" cost_entry: - other: "Cost entries" + other: "Mục nhập chi phí" cost_type: other: "Loại chi phí" time_entry_activity: - other: "Theo dõi hoạt động" - rate: "Tỷ lệ" + other: "Hoạt động theo dõi thời gian" + rate: "tỷ lệ" errors: models: time_entry: - invalid_time: "phải ở giữa khoảng 00:00 đến 23:59." - cannot_log_for_this_work_package: "Cannot log time for this work package." - duplicate_ongoing: "An ongoing time entry already exists for this user." + invalid_time: "phải trong khoảng từ 00:00 đến 23:59." + cannot_log_for_this_work_package: "Không thể ghi lại thời gian cho gói công việc này." + duplicate_ongoing: "Mục nhập thời gian liên tục đã tồn tại cho người dùng này." work_package: - is_not_a_valid_target_for_cost_entries: "Gói công việc #%{id} không phải là mục tiêu hợp lệ để phân bổ lại các mục chi phí." + is_not_a_valid_target_for_cost_entries: "Gói công việc #%{id} không phải là mục tiêu hợp lệ để chỉ định lại các mục chi phí." nullify_is_not_valid_for_cost_entries: "Các mục chi phí không thể được phân bổ cho một dự án." attributes: - comment: "Nhận xét" + comment: "bình luận" cost_type: "Loại chi phí" costs: "Chi phí" current_rate: "Tỷ giá hiện tại" - hours: "Giờ" - units: "Đơn vị" + hours: "giờ" + units: "đơn vị" valid_from: "Có hiệu lực từ" fixed_date: "Ngày cố định" button_add_rate: "Thêm tỷ lệ" button_log_costs: "Ghi lại chi phí đơn vị" - button_log_time: "Nhật ký" - button_add_activity: "Hoạt động" - button_add_time_entry: "Thời gian truy cập" - button_stop_timer: "Stop timer" - caption_booked_on_project: "Đã ghi trên dự án" - caption_default: "Mặc định" + button_log_time: "nhật ký" + button_add_activity: "hoạt động" + button_add_time_entry: "Đăng nhập thời gian" + button_stop_timer: "Dừng hẹn giờ" + caption_booked_on_project: "Đã đặt chỗ trên dự án" + caption_default: "mặc định" caption_default_rate_history_for: "Lịch sử tỷ lệ mặc định cho %{user}" - caption_locked_on: "Đã khóa vào" - caption_materials: "Đơn vị" - caption_rate_history: "Lịch sử đánh giá" - caption_rate_history_for: "Lịch sử tỷ lệ cho %{user}" - caption_rate_history_for_project: "Lịch sử tỷ lệ cho %{user} trong dự án %{project}" + caption_locked_on: "Đã khóa" + caption_materials: "đơn vị" + caption_rate_history: "Lịch sử giá" + caption_rate_history_for: "Lịch sử giá cho %{user}" + caption_rate_history_for_project: "Lịch sử giá cho %{user} trong dự án %{project}" caption_save_rate: "Lưu tỷ lệ" caption_set_rate: "Đặt tỷ lệ hiện tại" - caption_show_locked: "Hiển thị các loại đã khóa" - caption_log_time_dialog: "Thời gian truy cập" - description_date_for_new_rate: "Ngày cho tỷ lệ mới" - description_costs_settings: "Define the desired format for the costs in all projects." - description_time_settings: "Define which fields are mandatory to fill when logging time in all projects." + caption_show_locked: "Hiển thị các loại bị khóa" + caption_log_time_dialog: "Đăng nhập thời gian" + description_date_for_new_rate: "Ngày áp dụng mức giá mới" + description_costs_settings: "Xác định định dạng mong muốn cho chi phí trong tất cả các dự án." + description_time_settings: "Xác định những trường bắt buộc phải điền khi ghi thời gian vào tất cả các dự án." group_by_others: "không thuộc nhóm nào" label_between: "giữa" - label_cost_filter_add: "Thêm bộ lọc mục chi phí" - label_costlog: "Chi phí đơn vị đã ghi" - label_cost_plural: "Chi phí" + label_cost_filter_add: "Thêm bộ lọc mục nhập chi phí" + label_costlog: "Chi phí đơn vị được ghi lại" + label_cost_plural: "chi phí" label_cost_type_plural: "Loại chi phí" label_cost_type_specific: "Loại chi phí #%{id}: %{name}" - label_costs_per_page: "Chi phí trên mỗi trang" + label_costs_per_page: "Chi phí mỗi trang" label_current_default_rate: "Tỷ lệ mặc định hiện tại" - label_date_on: "vào" + label_date_on: "trên" label_deleted_cost_types: "Các loại chi phí đã xóa" - label_locked_cost_types: "Các loại chi phí đã khóa" - label_display_cost_entries: "Hiển thị chi phí đơn vị" - label_display_time_entries: "Hiển thị giờ đã báo cáo" - label_display_types: "Hiển thị loại" + label_locked_cost_types: "Các loại chi phí bị khóa" + label_display_cost_entries: "Chi phí đơn vị hiển thị" + label_display_time_entries: "Hiển thị số giờ được báo cáo" + label_display_types: "Các loại màn hình" label_edit: "Chỉnh sửa" label_generic_user: "Người dùng chung" label_greater_or_equal: ">=" label_group_by: "Nhóm theo" label_group_by_add: "Thêm trường nhóm" - label_hourly_rate: "Tỷ lệ theo giờ" - label_include_deleted: "Bao gồm các mục đã xóa" + label_hourly_rate: "Tỷ lệ hàng giờ" + label_include_deleted: "Bao gồm đã xóa" label_work_package_filter_add: "Thêm bộ lọc gói công việc" - label_kind: "Kiểu" + label_kind: "loại" label_less_or_equal: "<=" label_log_costs: "Ghi lại chi phí đơn vị" - label_new_time_entry_activity: "New time entry activity" - label_time_entry_activity_form_description: "Changes to this time entry activity will be reflected in all projects where it is enabled." - label_no: "Không" - label_option_plural: "Tuỳ chọn" + label_new_time_entry_activity: "Hoạt động nhập thời gian mới" + label_time_entry_activity_form_description: "Những thay đổi đối với hoạt động mục nhập thời gian này sẽ được phản ánh trong tất cả các dự án được kích hoạt." + label_no: "không" + label_option_plural: "tùy chọn" label_overall_costs: "Tổng chi phí" - label_rate: "Tỉ giá" - label_rate_plural: "Các tỷ lệ" - label_status_finished: "Hoàn thành" - label_show: "Hiện" + label_rate: "tỷ lệ" + label_rate_plural: "tỷ lệ" + label_status_finished: "Đã hoàn thành" + label_show: "Hiển thị" label_units: "Đơn vị chi phí" - label_user: "Người dùng" - label_until: "đến" + label_user: "người dùng" + label_until: "cho đến khi" label_valid_from: "Có hiệu lực từ" label_yes: "Có" - label_time: "Thời gian" - label_cost: "Cost" - label_costs: "Chi phí" - label_mandatory_fields: "Mandatory fields" - label_my_time_tracking: "My time tracking" - label_no_time: "No time tracked" - label_next_day: "Next day" + label_time: "thời gian" + label_cost: "Chi phí" + label_costs: "chi phí" + label_mandatory_fields: "Các trường bắt buộc" + label_my_time_tracking: "Theo dõi thời gian của tôi" + label_no_time: "Không theo dõi thời gian" + label_next_day: "Ngày hôm sau" label_next_week: "Tuần tới" - label_next_workweek: "Next work week" - label_next_month: "Next month" - label_previous_day: "Previous day" - label_previous_workweek: "Previous work week" + label_next_workweek: "Tuần làm việc tiếp theo" + label_next_month: "Tháng tiếp theo" + label_previous_day: "Ngày hôm trước" + label_previous_workweek: "Tuần làm việc trước đó" label_previous_week: "Tuần trước" - label_previous_month: "Previous month" - label_specific_day: "Day %{day}" - label_specific_week: "Calendar week %{week}" - label_specific_month: "Month %{month}" - label_list: "Danh sách" - label_month: "Tháng" - label_workweek: "Tuần làm việc" - label_day: "Ngày" - label_today_capitalized: "Hôm nay" - label_view_mode_switcher: "Change view" - label_timer_since: "Started at %{time}" - placeholder_activity_select_work_package_first: Phải chọn gói công việc trước đã + label_previous_month: "Tháng trước" + label_specific_day: "Ngày %{day}" + label_specific_week: "Tuần dương lịch %{week}" + label_specific_month: "Tháng %{month}" + label_list: "danh sách" + label_month: "tháng" + label_workweek: "tuần làm việc" + label_day: "ngày" + label_today_capitalized: "hôm nay" + label_view_mode_switcher: "Thay đổi chế độ xem" + label_timer_since: "Bắt đầu lúc %{time}" + placeholder_activity_select_work_package_first: Lựa chọn gói công việc là bắt buộc trước tiên notice_something_wrong: "Đã xảy ra lỗi. Vui lòng thử lại." notice_successful_restore: "Khôi phục thành công." - notice_successful_lock: "Khóa thành công." - notice_cost_logged_successfully: "Chi phí đơn vị đã được ghi lại thành công." - notice_different_time_zones: "Người dùng này có múi giờ khác (%{tz}). Nhật ký sẽ được ghi lại theo múi giờ của họ." - notice_time_entry_update_failed: "Failed to update time entry. Errors: %{errors}" - notice_time_entry_delete_failed: "Failed to delete time entry. Errors: %{errors}" - permission_edit_cost_entries: "Chỉnh sửa chi phí đơn vị đã ghi" - permission_edit_own_cost_entries: "Chỉnh sửa chi phí đơn vị đã ghi của chính mình" - permission_edit_hourly_rates: "Chỉnh sửa tỷ lệ theo giờ" - permission_edit_own_hourly_rate: "Chỉnh sửa tỷ lệ theo giờ của chính mình" - permission_edit_rates: "Chỉnh sửa các tỷ lệ" - permission_log_costs: "Ghi lại chi phí đơn vị" - permission_log_own_costs: "Ghi lại chi phí đơn vị cho chính mình" - permission_view_cost_entries: "Xem chi phí đã ghi" - permission_view_cost_rates: "Xem các tỷ lệ chi phí" - permission_view_hourly_rates: "Xem tất cả các tỷ lệ theo giờ" - permission_view_own_cost_entries: "Xem chi phí đã ghi của chính mình" - permission_view_own_hourly_rate: "Xem tỷ lệ theo giờ của chính mình" - permission_view_own_time_entries: "Xem thời gian đã tiêu của chính mình" + notice_successful_lock: "Đã khóa thành công." + notice_cost_logged_successfully: "Đơn giá được ghi thành công." + notice_different_time_zones: "Người dùng này có múi giờ khác (%{tz}). Thời gian sẽ được ghi lại bằng múi giờ của họ." + notice_time_entry_update_failed: "Không thể cập nhật mục nhập thời gian. Lỗi: %{errors}" + notice_time_entry_delete_failed: "Không thể xóa mục nhập thời gian. Lỗi: %{errors}" + permission_edit_cost_entries: "Chỉnh sửa chi phí đơn vị đã đặt" + permission_edit_own_cost_entries: "Chỉnh sửa chi phí đơn vị đã đặt riêng" + permission_edit_hourly_rates: "Chỉnh sửa mức giá theo giờ" + permission_edit_own_hourly_rate: "Chỉnh sửa mức giá theo giờ của riêng bạn" + permission_edit_rates: "Chỉnh sửa giá" + permission_log_costs: "Chi phí đơn vị sổ sách" + permission_log_own_costs: "Đặt chi phí đơn vị cho chính mình" + permission_view_cost_entries: "Xem chi phí đã đặt" + permission_view_cost_rates: "Xem mức chi phí" + permission_view_hourly_rates: "Xem tất cả mức giá theo giờ" + permission_view_own_cost_entries: "Xem chi phí đã đặt riêng" + permission_view_own_hourly_rate: "Xem tỷ lệ hàng giờ của riêng bạn" + permission_view_own_time_entries: "Xem thời gian đã sử dụng của chính bạn" project_module_costs: "Thời gian và chi phí" - setting_allow_tracking_start_and_end_times: "Allow start and finish times" - setting_costs_currency: "Tiền tệ" + setting_allow_tracking_start_and_end_times: "Cho phép thời gian bắt đầu và kết thúc" + setting_costs_currency: "tiền tệ" setting_costs_currency_format: "Định dạng tiền tệ" - setting_enforce_tracking_start_and_end_times: "Require start and finish times" - setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" - setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." - setting_enforce_tracking_start_and_end_times_caption: "Makes entering start and finish times mandatory when logging time." - 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ì?" - text_destroy_time_and_cost_entries: "Xóa giờ và chi phí đã báo cáo" - text_destroy_time_and_cost_entries_question: "%{hours} giờ, %{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ì?" - text_reassign_time_and_cost_entries: "Gán lại giờ và chi phí đã báo cáo cho gói công việc này:" - text_warning_hidden_elements: "Một số mục có thể đã bị loại trừ khỏi tổng hợp." + setting_enforce_tracking_start_and_end_times: "Yêu cầu thời gian bắt đầu và kết thúc" + setting_enforce_without_allow: "Không thể yêu cầu thời gian bắt đầu và kết thúc nếu không cho phép chúng" + setting_allow_tracking_start_and_end_times_caption: "Cho phép nhập thời gian bắt đầu và kết thúc khi ghi thời gian." + setting_enforce_tracking_start_and_end_times_caption: "Bắt buộc phải nhập thời gian bắt đầu và kết thúc khi ghi thời gian." + text_assign_time_and_cost_entries_to_project: "Chỉ định số giờ và chi phí được báo cáo cho dự án" + text_destroy_cost_entries_question: "%{cost_entries} đã được báo cáo về các gói công việc bạn sắp xóa. Bạn muốn làm gì?" + text_destroy_time_and_cost_entries: "Xóa số giờ và chi phí được báo cáo" + text_destroy_time_and_cost_entries_question: "%{hours} giờ, %{cost_entries} đã được báo cáo về các gói công việc bạn sắp xóa. Bạn muốn làm gì?" + text_reassign_time_and_cost_entries: "Chỉ định lại số giờ và chi phí được báo cáo cho gói công việc này:" + text_warning_hidden_elements: "Một số mục có thể đã bị loại khỏi tổng hợp." week: "tuần" total_times: - workweek: "Workweek Total: %{hours}" - week: "Week Total: %{hours}" - month: "Month Total: %{hours}" - day: "Day Total: %{hours}" + workweek: "Tổng số tuần làm việc: %{hours}" + week: "Tổng số tuần: %{hours}" + month: "Tổng số tháng: %{hours}" + day: "Tổng số ngày: %{hours}" api_v3: errors: validation: - start_time_different_date: "Phần ngày của thời gian bắt đầu (%{start_time}) phải giống với ngày tiêu phí (%{spent_on})." + start_time_different_date: "Phần ngày của thời gian bắt đầu (%{start_time}) phải giống với ngày chi tiêu (%{spent_on})." ee: features: - time_entry_time_restrictions: Require exact time tracking + time_entry_time_restrictions: Yêu cầu theo dõi thời gian chính xác upsell: time_entry_time_restrictions: - description: "Improve your time tracking by requiring exact start and finish times when logging time." + description: "Cải thiện việc theo dõi thời gian của bạn bằng cách yêu cầu thời gian bắt đầu và kết thúc chính xác khi ghi thời gian." diff --git a/modules/documents/config/locales/crowdin/it.yml b/modules/documents/config/locales/crowdin/it.yml index c58cf20e44b..59927bb626d 100644 --- a/modules/documents/config/locales/crowdin/it.yml +++ b/modules/documents/config/locales/crowdin/it.yml @@ -74,7 +74,7 @@ it: action: Riprova connection_recovery_notice: description: "La connessione al server della collaborazione in tempo reale sul testo è stata ripristinata." - tabs: "Document tabs" + tabs: "Schede dei documenti" index_page: name: "Nome" type: "Tipo" diff --git a/modules/documents/config/locales/crowdin/pl.yml b/modules/documents/config/locales/crowdin/pl.yml index ac69eae09c6..a2abc34be2f 100644 --- a/modules/documents/config/locales/crowdin/pl.yml +++ b/modules/documents/config/locales/crowdin/pl.yml @@ -74,7 +74,7 @@ pl: action: Spróbuj ponownie connection_recovery_notice: description: "Połączenie z serwerem współpracy tekstowej w czasie rzeczywistym zostało przywrócone." - tabs: "Document tabs" + tabs: "Karty dokumentów" index_page: name: "Nazwa" type: "Typ" diff --git a/modules/documents/config/locales/crowdin/vi.seeders.yml b/modules/documents/config/locales/crowdin/vi.seeders.yml index 4b75a6cf86c..8be06340b92 100644 --- a/modules/documents/config/locales/crowdin/vi.seeders.yml +++ b/modules/documents/config/locales/crowdin/vi.seeders.yml @@ -7,14 +7,14 @@ vi: common: document_types: item_0: - name: Note + name: Lưu ý item_1: - name: Idea + name: Ý tưởng item_2: - name: Proposal + name: Đề xuất item_3: - name: Specification + name: Đặc điểm kỹ thuật item_4: - name: Report + name: Báo cáo item_5: - name: Documentation + name: tài liệu diff --git a/modules/documents/config/locales/crowdin/vi.yml b/modules/documents/config/locales/crowdin/vi.yml index c630a52b52a..e68a354e318 100644 --- a/modules/documents/config/locales/crowdin/vi.yml +++ b/modules/documents/config/locales/crowdin/vi.yml @@ -21,133 +21,133 @@ #++ vi: plugin_openproject_documents: - name: "Tài liệu OpenProject" - description: "Một plugin OpenProject cho phép tạo tài liệu trong các dự án." + name: "Tài liệu dự án mở" + description: "Một plugin OpenProject để cho phép tạo tài liệu trong các dự án." activerecord: errors: models: document_type: - one_or_more_required: "Cannot delete the last document type" + one_or_more_required: "Không thể xóa loại tài liệu cuối cùng" models: - document: "Tài liệu" - documents: "Documents" + document: "tài liệu" + documents: "tài liệu" attributes: document: - content_binary: "Content binary" - title: "Tiêu đề" + content_binary: "Nội dung nhị phân" + title: "tiêu đề" activity: filter: - document: "Tài liệu" + document: "tài liệu" default_doc_category_tech: "Tài liệu kỹ thuật" default_doc_category_user: "Tài liệu người dùng" enumeration_doc_categories: "Danh mục tài liệu" documents: page_header: - heading: "All documents" + heading: "Tất cả tài liệu" action_menu: - document_actions: "Document actions" - edit_title: "Edit title" + document_actions: "Hành động tài liệu" + edit_title: "Chỉnh sửa tiêu đề" subheader: filter: - label: "Document name filter" - placeholder: "Type here to search document names" + label: "Bộ lọc tên tài liệu" + placeholder: "Nhập vào đây để tìm kiếm tên tài liệu" documents_list_blank_slate: - heading: "There are no documents yet" - description: "There are no documents in this view. You can click the button below to add one." + heading: "Chưa có tài liệu nào" + description: "Không có tài liệu nào trong chế độ xem này. Bạn có thể nhấp vào nút bên dưới để thêm một." document_categories_deprecation_notice: - heading: File categories are now called 'Document types' + heading: Các danh mục tệp hiện được gọi là 'Loại tài liệu' description: |- - Your existing file categories have been converted to document types with the introduction of the new Documents module. - All existing documents have also been migrated to these new types. - primary_action: Configure document types - secondary_action: Learn more about the Documents module - document_type_actions: "Document type actions" + Các danh mục tệp hiện có của bạn đã được chuyển đổi thành loại tài liệu với việc giới thiệu mô-đun Tài liệu mới. + Tất cả các tài liệu hiện có cũng đã được di chuyển sang các loại mới này. + primary_action: Định cấu hình các loại tài liệu + secondary_action: Tìm hiểu thêm về mô-đun Tài liệu + document_type_actions: "Hành động loại tài liệu" text_collaboration_disabled_notice: description: |- - Unable to open document because real-time text collaboration is disabled. - Please contact your administrator to enable real-time text collaboration if you want to access this document. + Không thể mở tài liệu vì cộng tác văn bản theo thời gian thực bị tắt. + Vui lòng liên hệ với quản trị viên của bạn để cho phép cộng tác văn bản theo thời gian thực nếu bạn muốn truy cập tài liệu này. show_edit_view: connection_error_notice: description: |- - Unable to open document because the real-time text collaboration server is unreachable. - Please contact the administrator if the problem persists. - action: Try again + Không thể mở tài liệu vì không thể truy cập được máy chủ cộng tác văn bản theo thời gian thực. + Vui lòng liên hệ với quản trị viên nếu sự cố vẫn tiếp diễn. + action: Thử lại connection_recovery_notice: - description: "The connection to the real-time text collaboration server has been restored." - tabs: "Document tabs" + description: "Kết nối tới máy chủ cộng tác văn bản thời gian thực đã được khôi phục." + tabs: "Tab tài liệu" index_page: - name: "Tên" - type: "Kiểu" - updated_at: "Last edited" - label_legacy: "Legacy" + name: "tên" + type: "loại" + updated_at: "Chỉnh sửa lần cuối" + label_legacy: "Di sản" menu: - all: "All documents" - types: "Các loại" - collaboration_settings: "Real-time collaboration" - last_updated_at: "Last saved %{time}." - active_editors: "Active editors" + all: "Tất cả tài liệu" + types: "các loại" + collaboration_settings: "Hợp tác thời gian thực" + last_updated_at: "Đã lưu lần cuối %{time}." + active_editors: "Người chỉnh sửa đang hoạt động" active_editors_count: - other: "%{count} active editors" - label_attachment_author: "Tác giả tài liệu đính kèm" - label_categories: "Categories" + other: "%{count} biên tập viên đang hoạt động" + label_attachment_author: "Tác giả đính kèm" + label_categories: "danh mục" new_category: "Danh mục mới" - new_type: "New type" + new_type: "Loại mới" delete_dialog: - title: "Delete document" - heading: "Delete this document?" - confirmation_message_html: "This will permanently delete this document and all file attachments. Are you sure you want to do this?" + title: "Xóa tài liệu" + heading: "Xóa tài liệu này?" + confirmation_message_html: "Thao tác này sẽ xóa vĩnh viễn tài liệu này và tất cả tệp đính kèm. Bạn có chắc chắn muốn làm điều này?" delete_document_type_dialog: - title: Delete document type - heading: Delete this document type? + title: Xóa loại tài liệu + heading: Xóa loại tài liệu này? confirmation_message: |- - The type "%{type_name}" is currently unused. Deleting this type will have no effect on existing documents. - select_reassign_to_label: Reassign documents to + Loại "%{type_name}" hiện không được sử dụng. Việc xóa loại này sẽ không có hiệu lực đối với các tài liệu hiện có. + select_reassign_to_label: Phân công lại tài liệu cho reassign_message: - other: The type "%{type_name}" is currently being used in %{document_count} documents. Please select which type to reassign them to. + other: Loại "%{type_name}" hiện đang được sử dụng trong tài liệu %{document_count}. Vui lòng chọn loại để gán lại chúng. at_least_one_type_required: - title: Cannot delete document type - heading: Cannot delete the last document type - message: There must always be at least one document type configured. Create another one first if you want to delete this one. + title: Không thể xóa loại tài liệu + heading: Không thể xóa loại tài liệu cuối cùng + message: Phải luôn có ít nhất một loại tài liệu được định cấu hình. Tạo một cái khác trước nếu bạn muốn xóa cái này. admin: collaboration_settings: page_header: description: |- - When enabled, real-time collaboration allows multiple users to edit a document at the same time. - It requires a working %{hocuspocus_server_link} to function. + Khi được bật, cộng tác theo thời gian thực cho phép nhiều người dùng chỉnh sửa tài liệu cùng một lúc. + Nó yêu cầu %{hocuspocus_server_link} hoạt động. banner: - none_writable: These values are configured via environment variables and cannot be edited here. - some_unwritable: Some values are configured via environment variables and cannot be edited here. + none_writable: Các giá trị này được định cấu hình thông qua các biến môi trường và không thể chỉnh sửa ở đây. + some_unwritable: Một số giá trị được định cấu hình thông qua biến môi trường và không thể chỉnh sửa ở đây. hocuspocus_server_url: - label: "Hocuspocus server URL" - caption: "The address of a working Hocuspocus server." + label: "URL máy chủ Hocuspocus" + caption: "Địa chỉ của máy chủ Hocuspocus đang hoạt động." hocuspocus_server_secret: - label: "Client secret" - caption: "Paste the secret provided by the Hocuspocus server." - hocuspocus_server: Hocuspocus server + label: "Bí mật của khách hàng" + caption: "Dán bí mật được cung cấp bởi máy chủ Hocuspocus." + hocuspocus_server: máy chủ hocuspocus enable_text_collaboration: - heading: Real-time collaboration is not enabled + heading: Cộng tác thời gian thực chưa được bật description: |- - Once enabled, multiple users will be able to work together on a document at the same time. - All new documents will be based on a new editor (BlockNote) and will require a working connection to a - Hocuspocus server. - primary_action: Enable real-time collaboration - success: Real-time collaboration has been enabled. + Sau khi được bật, nhiều người dùng sẽ có thể làm việc cùng nhau trên một tài liệu cùng một lúc. + Tất cả các tài liệu mới sẽ dựa trên trình chỉnh sửa mới (BlockNote) và sẽ yêu cầu kết nối hoạt động với + Máy chủ hocuspocus. + primary_action: Cho phép cộng tác theo thời gian thực + success: Cộng tác thời gian thực đã được kích hoạt. disable_text_collaboration_dialog: - title: Disable real-time collaboration - heading: Disable real-time collaboration? + title: Vô hiệu hóa cộng tác thời gian thực + heading: Vô hiệu hóa cộng tác thời gian thực? confirmation_message: |- - All existing documents may become inaccessible. Please only do this if you are certain you want to disable - real-time collaboration and the BlockNote editor in this instance. - confirmation_checkbox_message: I understand that I might permanently lose data - success: Real-time collaboration has been disabled. - label_document_added: "Tài liệu đã được thêm" + Tất cả các tài liệu hiện có có thể không thể truy cập được. Vui lòng chỉ thực hiện việc này nếu bạn chắc chắn muốn tắt + cộng tác thời gian thực và trình soạn thảo BlockNote trong trường hợp này. + confirmation_checkbox_message: Tôi hiểu rằng tôi có thể mất dữ liệu vĩnh viễn + success: Cộng tác thời gian thực đã bị vô hiệu hóa. + label_document_added: "Đã thêm tài liệu" label_document_new: "Tài liệu mới" - label_document_plural: "Tài liệu" - label_documents: "Tài liệu" - label_document_title: "Tiêu đề" - label_document_description: "Mô tả" - label_document_category: "Thể loại" - label_document_type: "Type" + label_document_plural: "tài liệu" + label_documents: "tài liệu" + label_document_title: "tiêu đề" + label_document_description: "mô tả" + label_document_category: "thể loại" + label_document_type: "loại" permission_manage_documents: "Quản lý tài liệu" permission_view_documents: "Xem tài liệu" - project_module_documents: "Tài liệu" + project_module_documents: "tài liệu" diff --git a/modules/documents/config/locales/crowdin/zh-CN.yml b/modules/documents/config/locales/crowdin/zh-CN.yml index e400d88d741..c48066b2ad6 100644 --- a/modules/documents/config/locales/crowdin/zh-CN.yml +++ b/modules/documents/config/locales/crowdin/zh-CN.yml @@ -53,7 +53,7 @@ zh-CN: placeholder: "在此输入搜索文档名称" documents_list_blank_slate: heading: "还没有文档" - description: "此视图中没有文档。您可以点击下面的按钮添加一个文档。" + description: "此视图中没有文档。您可以点击下方按钮添加一个文档。" document_categories_deprecation_notice: heading: 文件类别现在称为“文档类型” description: |- @@ -74,7 +74,7 @@ zh-CN: action: 重试 connection_recovery_notice: description: "与实时文本协作服务器的连接已恢复。" - tabs: "Document tabs" + tabs: "“文档”选项卡" index_page: name: "名称" type: "类型" diff --git a/modules/gantt/config/locales/crowdin/js-vi.yml b/modules/gantt/config/locales/crowdin/js-vi.yml index eb33fd649b7..c7a56d4199b 100644 --- a/modules/gantt/config/locales/crowdin/js-vi.yml +++ b/modules/gantt/config/locales/crowdin/js-vi.yml @@ -1,6 +1,6 @@ vi: js: work_packages: - label_gantt_chart_plural: "Biểu đồ Gantt" + label_gantt_chart_plural: "biểu đồ Gantt" default_queries: - milestones: 'Các mốc thời gian' + milestones: 'Các cột mốc quan trọng' diff --git a/modules/gantt/config/locales/crowdin/vi.yml b/modules/gantt/config/locales/crowdin/vi.yml index 9cef90f2665..0f61027962c 100644 --- a/modules/gantt/config/locales/crowdin/vi.yml +++ b/modules/gantt/config/locales/crowdin/vi.yml @@ -1,3 +1,3 @@ #English strings go here vi: - project_module_gantt: "Biểu đồ Gantt" + project_module_gantt: "biểu đồ Gantt" diff --git a/modules/github_integration/config/locales/crowdin/js-vi.yml b/modules/github_integration/config/locales/crowdin/js-vi.yml index 1b645ed0ce6..cad73e19352 100644 --- a/modules/github_integration/config/locales/crowdin/js-vi.yml +++ b/modules/github_integration/config/locales/crowdin/js-vi.yml @@ -25,30 +25,30 @@ vi: work_packages: tab_name: "GitHub" tab_header: - title: "Yêu cầu kéo" + title: "Kéo yêu cầu" copy_menu: label: Đoạn mã Git - description: Sao chép đoạn mã Git vào bảng tạm + description: Sao chép đoạn git vào clipboard git_actions: - branch_name: Tên nhánh - commit_message: Thông điệp commit - cmd: Tạo nhánh với commit trống + branch_name: Tên chi nhánh + commit_message: Thông báo cam kết + cmd: Tạo nhánh với cam kết trống title: Đoạn mã nhanh cho Git copy_success: '✅ Đã sao chép!' copy_error: '❌ Sao chép thất bại!' tab_prs: - empty: 'Chưa có yêu cầu kéo nào được liên kết. Liên kết một PR hiện có bằng cách sử dụng mã OP#%{wp_id} trong mô tả PR hoặc tạo một PR mới.' - github_actions: Hành động + empty: 'Chưa có yêu cầu kéo nào được liên kết. Liên kết một PR hiện có bằng cách sử dụng mã OP#%{wp_id} trong phần mô tả PR hoặc tạo một PR mới.' + github_actions: hành động pull_requests: - message: "Yêu cầu kéo #%{pr_number} %{pr_link} cho %{repository_link} do %{github_user_link} viết đã được %{pr_state}." + message: "Yêu cầu kéo #%{pr_number} %{pr_link} cho %{repository_link} do %{github_user_link} tạo đã được %{pr_state}." merged_message: "Yêu cầu kéo #%{pr_number} %{pr_link} cho %{repository_link} đã được %{pr_state} bởi %{github_user_link}." - referenced_message: "Yêu cầu kéo #%{pr_number} %{pr_link} cho %{repository_link} do %{github_user_link} viết đã tham chiếu đến gói công việc này." + referenced_message: "Kéo yêu cầu #%{pr_number} %{pr_link} cho %{repository_link} tác giả bởi %{github_user_link} đã tham chiếu gói công việc này." states: - opened: 'mở' - closed: 'đã đóng' - draft: 'dự thảo' - merged: 'gộp' - ready_for_review: 'đánh dấu sẵn sàng để xem xét' + opened: 'đã mở' + closed: 'đóng cửa' + draft: 'soạn thảo' + merged: 'sáp nhập' + ready_for_review: 'được đánh dấu sẵn sàng để xem xét' work_packages: tabs: github: "GitHub" diff --git a/modules/github_integration/config/locales/crowdin/vi.yml b/modules/github_integration/config/locales/crowdin/vi.yml index 389a250cc0b..1224ff65ffe 100644 --- a/modules/github_integration/config/locales/crowdin/vi.yml +++ b/modules/github_integration/config/locales/crowdin/vi.yml @@ -21,35 +21,35 @@ #++ vi: attributes: - additions_count: "Number of additions" - deletions_count: "Number of deletions" - comments_count: "Comments count" - github_html_url: "Pull Request URL" - github_app_owner_avatar_url: "Owner avatar URL" - review_comments_count: "Review comments count" - github: "GitHub identifer" - github_avatar_url: "Avatar URL" - github_updated_at: "Updated at" - github_login: "GitHub login" - host: "Máy chủ" - labels: "Labels" - repository: "Kho lưu trữ" - changed_files_count: "Changed files" - button_add_deploy_target: Thêm mục triển khai - label_deploy_target: Mục triển khai - label_deploy_target_new: Mục triển khai mới - label_deploy_target_plural: Các mục triển khai + additions_count: "Số lần bổ sung" + deletions_count: "Số lần xóa" + comments_count: "Số lượng bình luận" + github_html_url: "Kéo URL yêu cầu" + github_app_owner_avatar_url: "URL hình đại diện của chủ sở hữu" + review_comments_count: "Xem lại số lượng bình luận" + github: "Mã định danh GitHub" + github_avatar_url: "URL hình đại diện" + github_updated_at: "Cập nhật lúc" + github_login: "đăng nhập GitHub" + host: "chủ nhà" + labels: "Nhãn" + repository: "kho lưu trữ" + changed_files_count: "Tập tin đã thay đổi" + button_add_deploy_target: Thêm mục tiêu triển khai + label_deploy_target: Triển khai mục tiêu + label_deploy_target_new: Mục tiêu triển khai mới + label_deploy_target_plural: Triển khai mục tiêu label_github_integration: Tích hợp GitHub - notice_deploy_target_created: Mục triển khai đã được tạo - notice_deploy_target_destroyed: Mục triển khai đã bị xóa + notice_deploy_target_created: Đã tạo mục tiêu triển khai + notice_deploy_target_destroyed: Đã xóa mục tiêu triển khai plugin_openproject_github_integration: - name: "Tích hợp GitHub OpenProject" + name: "Tích hợp GitHub của OpenProject" description: "Tích hợp OpenProject và GitHub để có quy trình làm việc tốt hơn" project_module_github: "GitHub" permission_show_github_content: "Hiển thị nội dung GitHub" - permission_introspection: Đọc phiên bản OpenProject đang chạy và SHA build + permission_introspection: Đọc phiên bản lõi OpenProject đang chạy và xây dựng SHA text_deploy_target_type_info: > - Hiện tại chúng tôi chỉ hỗ trợ OpenProject. + Cho đến nay chúng tôi chỉ hỗ trợ OpenProject. text_deploy_target_api_key_info: > - Một [API key](docs_url) của OpenProject thuộc về người dùng có quyền introspection toàn cầu. - text_pull_request_deployed_to: "%{pr_link} đã được triển khai tới %{deploy_target_link}" + OpenProject [API key](docs_url) thuộc về người dùng có quyền xem xét nội tâm toàn cầu. + text_pull_request_deployed_to: "%{pr_link} được triển khai tới %{deploy_target_link}" diff --git a/modules/gitlab_integration/config/locales/crowdin/js-vi.yml b/modules/gitlab_integration/config/locales/crowdin/js-vi.yml index 08e2eba7df9..781ce1d6ea3 100644 --- a/modules/gitlab_integration/config/locales/crowdin/js-vi.yml +++ b/modules/gitlab_integration/config/locales/crowdin/js-vi.yml @@ -26,28 +26,28 @@ vi: work_packages: tab_name: "GitLab" tab_header_issue: - title: "Các vấn đề" + title: "vấn đề" tab_header_mr: - title: "Yêu cầu kéo" + title: "Hợp nhất các yêu cầu" create_mr: label: Tạo MR - description: Tạo một Yêu cầu Kéo + description: Tạo yêu cầu hợp nhất copy_menu: label: Mã git - description: Sao chép mã git vào clipboard + description: Sao chép đoạn git vào clipboard git_actions: - branch_name: Tên nhánh - commit_message: Tin nhắn commit - cmd: Tạo nhánh với commit trống + branch_name: Tên chi nhánh + commit_message: Thông báo cam kết + cmd: Tạo nhánh với cam kết trống title: Mã nhanh cho Git - copy_success: '✅ Đã sao chép!' - copy_error: '❌ Sao chép thất bại!' + copy_success: '✅ Sao chép!' + copy_error: '❌ Sao chép không thành công!' tab_issue: - empty: 'Chưa có vấn đề nào được liên kết. Liên kết một vấn đề hiện có bằng cách sử dụng mã OP#%{wp_id} (hoặc PP#%{wp_id} cho liên kết riêng) trong tiêu đề/mô tả vấn đề hoặc tạo một vấn đề mới.' + empty: 'Chưa có vấn đề nào được liên kết. Liên kết một vấn đề hiện có bằng cách sử dụng mã OP#%{wp_id} (hoặc PP#%{wp_id}_ đối với các liên kết riêng tư) trong tiêu đề/mô tả vấn đề hoặc tạo một vấn đề mới.' tab_mrs: - empty: 'Chưa có yêu cầu kéo nào được liên kết. Liên kết một MR hiện có bằng cách sử dụng mã OP#%{wp_id} (hoặc PP#%{wp_id} cho liên kết riêng) trong tiêu đề/mô tả MR hoặc tạo một MR mới.' - gitlab_pipelines: Pipelines - updated_on: Được cập nhật vào lúc + empty: 'Chưa có yêu cầu hợp nhất nào được liên kết. Liên kết MR hiện có bằng cách sử dụng mã OP#%{wp_id} (hoặc PP#%{wp_id}_ cho các liên kết riêng tư) trong tiêu đề/mô tả MR hoặc tạo MR mới.' + gitlab_pipelines: Đường ống + updated_on: Đã cập nhật vào work_packages: tabs: gitlab: "GitLab" diff --git a/modules/gitlab_integration/config/locales/crowdin/vi.yml b/modules/gitlab_integration/config/locales/crowdin/vi.yml index 0caf9d01995..0694435d920 100644 --- a/modules/gitlab_integration/config/locales/crowdin/vi.yml +++ b/modules/gitlab_integration/config/locales/crowdin/vi.yml @@ -22,32 +22,32 @@ #++ vi: attributes: - commit: "Commit" - gitlab: "GitLab identifier" - gitlab_avatar_url: "Avatar URL" - gitlab_email: "GitLab Email" - gitlab_html_url: "GitLab HTML URL" - gitlab_merge_request: "Merge Request" - gitlab_name: "GitLab name" - gitlab_updated_at: "Updated at" - gitlab_user_avatar_url: "GitLab avatar URL" - gitlab_username: "GitLab username" + commit: "cam kết" + gitlab: "Mã định danh GitLab" + gitlab_avatar_url: "URL hình đại diện" + gitlab_email: "Email của GitLab" + gitlab_html_url: "URL HTML của GitLab" + gitlab_merge_request: "Yêu cầu hợp nhất" + gitlab_name: "Tên GitLab" + gitlab_updated_at: "Cập nhật lúc" + gitlab_user_avatar_url: "URL hình đại diện GitLab" + gitlab_username: "tên người dùng GitLab" activerecord: attributes: gitlab_pipeline: - ci_details: "CI Details" + ci_details: "Chi tiết CI" gitlab_merge_request: - labels: "Labels" + labels: "Nhãn" errors: models: gitlab_issue: attributes: labels: - invalid_schema: "phải là một mảng các hash với các khóa: màu, tiêu đề" + invalid_schema: "phải là một mảng băm có khóa: màu sắc, tiêu đề" gitlab_merge_request: attributes: labels: - invalid_schema: "phải là một mảng các hash với các khóa: màu, tiêu đề" + invalid_schema: "phải là một mảng băm có khóa: màu sắc, tiêu đề" project_module_gitlab: "GitLab" permission_show_gitlab_content: "Hiển thị nội dung GitLab" gitlab_integration: diff --git a/modules/grids/config/locales/crowdin/js-pl.yml b/modules/grids/config/locales/crowdin/js-pl.yml index 209ca6ffc2c..c91e19830d4 100644 --- a/modules/grids/config/locales/crowdin/js-pl.yml +++ b/modules/grids/config/locales/crowdin/js-pl.yml @@ -16,7 +16,7 @@ pl: news: title: 'Aktualności' project_description: - title: 'Description' + title: 'Opis' no_results: "Nie napisano jeszcze opisu. Można go podać w sekcji Ustawienia projektu." project_status: title: 'Status' diff --git a/modules/grids/config/locales/crowdin/js-vi.yml b/modules/grids/config/locales/crowdin/js-vi.yml index a4607d6a541..51927944263 100644 --- a/modules/grids/config/locales/crowdin/js-vi.yml +++ b/modules/grids/config/locales/crowdin/js-vi.yml @@ -1,45 +1,45 @@ vi: js: grid: - add_widget: 'Thêm widget' - remove: 'Xóa widget' - configure: 'Cấu hình widget' + add_widget: 'Thêm tiện ích' + remove: 'Xóa tiện ích' + configure: 'Định cấu hình tiện ích' widgets: - missing_permission: "Bạn không có quyền cần thiết để xem widget này." + missing_permission: "Bạn không có quyền cần thiết để xem tiện ích này." custom_text: title: 'Văn bản tùy chỉnh' documents: title: 'Tài liệu' no_results: 'Chưa có tài liệu.' members: - title: 'Thành viên' + title: 'thành viên' news: - title: 'Tin tức' + title: 'tin tức' project_description: - title: 'Description' - no_results: "Chưa có mô tả nào được viết. Có thể cung cấp trong 'Cài đặt dự án'." + title: 'mô tả' + no_results: "Chưa có mô tả nào được viết. Một cái có thể được cung cấp trong 'Cài đặt dự án'." project_status: - title: 'Status' + title: 'trạng thái' not_started: 'Chưa bắt đầu' - on_track: 'Đang theo dõi' - off_track: 'Ra ngoài quỹ đạo' + on_track: 'Đang đi đúng hướng' + off_track: 'Lạc lối' at_risk: 'Có nguy cơ' not_set: 'Không được thiết lập' - finished: 'Hoàn thành' - discontinued: 'Ngừng' + finished: 'Đã hoàn thành' + discontinued: 'Đã ngừng sản xuất' project_status_beta: - title: 'Status (BETA)' + title: 'Trạng thái (BETA)' subprojects: - title: 'Subitems' + title: 'mục phụ' project_favorites: - title: 'Dự án yêu thích' - no_results: 'Hiện tại bạn không có dự án yêu thích nào. Nhấp vào biểu tượng sao trên bảng điều khiển dự án để thêm một dự án vào danh sách yêu thích của bạn.' + title: 'dự án yêu thích' + no_results: 'Hiện tại bạn chưa có dự án yêu thích nào. Nhấp vào biểu tượng ngôi sao trong bảng điều khiển dự án để thêm một biểu tượng vào mục yêu thích của bạn.' time_entries_current_user: title: 'Thời gian hiện tại (My spent time)' - displayed_days: 'Số ngày được hiển thị trong widget:' + displayed_days: 'Ngày hiển thị trong widget:' time_entries_list: - title: 'Thời gian làm việc (trong 7 ngày gần nhất)' - no_results: 'Không có ghi nhận thời gian làm việc trong 7 ngày gần nhất.' + title: 'Thời gian đã sử dụng (7 ngày qua)' + no_results: 'Không có mục thời gian trong 7 ngày qua.' work_packages_accountable: title: "Gói công việc mà tôi có trách nhiệm" work_packages_assigned: @@ -51,10 +51,10 @@ vi: work_packages_table: title: 'Bảng gói công việc' work_packages_graph: - title: 'Đồ thị gói công việc' - summary: "%{chartType} chart showing work packages which are %{description}." + title: 'Biểu đồ gói công việc' + summary: "Biểu đồ %{chartType} hiển thị các gói công việc %{description}." work_packages_calendar: - title: 'Lịch' + title: 'lịch' work_packages_overview: title: 'Tổng quan về gói công việc' - placeholder: 'Nhấp để chỉnh sửa ...' + placeholder: 'Bấm vào để chỉnh sửa...' diff --git a/modules/grids/config/locales/crowdin/vi.yml b/modules/grids/config/locales/crowdin/vi.yml index 31f3466525a..af9bc6f303f 100644 --- a/modules/grids/config/locales/crowdin/vi.yml +++ b/modules/grids/config/locales/crowdin/vi.yml @@ -1,21 +1,21 @@ vi: grids: - label_widget_in_grid: "Widget nằm trong Lưới %{grid_name}" + label_widget_in_grid: "Tiện ích có trong Lưới %{grid_name}" widgets: - empty: "This widget is currently empty." - not_available: "This widget is not available." + empty: "Tiện ích này hiện đang trống." + not_available: "Tiện ích này không có sẵn." subitems: - title: "Subitems" - no_results: "There are no visible children." - view_all_subitems: "View all subitems" - button_text: "Subitem" + title: "mục phụ" + no_results: "Không có trẻ em có thể nhìn thấy." + view_all_subitems: "Xem tất cả các mục con" + button_text: "mục con" members: - title: "Members" - no_results: "Không có thành viên nào được hiển thị." + title: "thành viên" + no_results: "Không có thành viên có thể nhìn thấy." view_all_members: "Xem tất cả thành viên" - show_members_count: "Show all %{count} members" + show_members_count: "Hiển thị tất cả thành viên %{count}" x_more: - other: "and %{count} more members." + other: "và %{count} thành viên khác." news: no_results: "Không có gì mới để báo cáo." activerecord: @@ -25,7 +25,7 @@ vi: row_count: "Số dòng" column_count: "Số cột" widgets: "Tiện ích" - scope: "Phạm vi" + scope: "phạm vi" errors: models: grids/grid: diff --git a/modules/job_status/config/locales/crowdin/vi.yml b/modules/job_status/config/locales/crowdin/vi.yml index e6fc5272e8b..127a8178439 100644 --- a/modules/job_status/config/locales/crowdin/vi.yml +++ b/modules/job_status/config/locales/crowdin/vi.yml @@ -1,21 +1,21 @@ vi: - label_job_status_plural: "Tình trạng công việc" + label_job_status_plural: "Trạng thái công việc" plugin_openproject_job_status: - name: "Tình trạng công việc OpenProject" + name: "Trạng thái công việc OpenProject" description: "Danh sách và trạng thái của các công việc nền." job_status_dialog: download_starts: 'Quá trình tải xuống sẽ tự động bắt đầu.' link_to_download: 'Hoặc %{link} để tải xuống.' - click_here: 'nhấn vào đây' + click_here: 'bấm vào đây' title: 'Trạng thái công việc nền' redirect: 'Bạn đang bị chuyển hướng.' redirect_link: 'Vui lòng nhấp vào đây để tiếp tục.' redirect_errors: 'Do những lỗi này, bạn sẽ không được chuyển hướng tự động.' - errors: 'Đã xảy ra sự cố' + errors: 'Đã xảy ra lỗi' generic_messages: - not_found: 'Không thể tìm thấy công việc này.' - in_queue: 'Công việc đã được xếp hàng đợi và sẽ sớm được xử lý.' + not_found: 'Công việc này không thể được tìm thấy.' + in_queue: 'Công việc đã được xếp hàng đợi và sẽ được xử lý ngay.' in_process: 'Công việc hiện đang được xử lý.' - error: 'Không thể hoàn thành công việc.' - cancelled: 'Công việc đã bị hủy do lỗi' + error: 'Công việc đã không thể hoàn thành.' + cancelled: 'Công việc đã bị hủy do có lỗi.' success: 'Công việc đã hoàn thành thành công.' diff --git a/modules/ldap_groups/config/locales/crowdin/vi.yml b/modules/ldap_groups/config/locales/crowdin/vi.yml index 13582a30da6..fd69f21f458 100644 --- a/modules/ldap_groups/config/locales/crowdin/vi.yml +++ b/modules/ldap_groups/config/locales/crowdin/vi.yml @@ -3,10 +3,10 @@ vi: upsell: ldap_groups: title: 'Đồng bộ hóa nhóm LDAP' - description: 'Synchronize LDAP groups with OpenProject groups to manage users, change their permissions and facilitate user management across groups.' + description: 'Đồng bộ hóa các nhóm LDAP với các nhóm OpenProject để quản lý người dùng, thay đổi quyền của họ và tạo điều kiện thuận lợi cho việc quản lý người dùng giữa các nhóm.' plugin_openproject_ldap_groups: name: "Nhóm LDAP OpenProject" - description: "Đồng bộ hóa các thành viên nhóm LDAP." + description: "Đồng bộ hóa tư cách thành viên nhóm LDAP." activerecord: attributes: ldap_groups/synchronized_group: @@ -20,61 +20,61 @@ vi: ldap_auth_source: 'Kết nối LDAP' group_name_attribute: "Thuộc tính tên nhóm" sync_users: 'Đồng bộ hóa người dùng' - base_dn: "Tìm kiếm base DN" + base_dn: "Tìm kiếm cơ sở DN" models: - ldap_groups/synchronized_group: 'Nhóm LDAP đã đồng bộ' + ldap_groups/synchronized_group: 'Nhóm LDAP được đồng bộ hóa' ldap_groups/synchronized_filter: 'Bộ lọc đồng bộ hóa nhóm LDAP' errors: models: ldap_groups/synchronized_filter: - must_contain_base_dn: "Base DN của bộ lọc phải nằm trong base DN của kết nối LDAP" + must_contain_base_dn: "DN cơ sở bộ lọc phải nằm trong DN cơ sở của kết nối LDAP" ldap_groups: label_menu_item: 'Đồng bộ hóa nhóm LDAP' label_group_key: 'Khóa bộ lọc nhóm LDAP' label_synchronize: 'Đồng bộ' settings: name_attribute: 'Thuộc tính tên nhóm LDAP' - name_attribute_text: 'Thuộc tính LDAP được sử dụng để đặt tên nhóm OpenProject khi được tạo bởi bộ lọc' + name_attribute_text: 'Thuộc tính LDAP được sử dụng để đặt tên cho nhóm OpenProject khi được tạo bởi bộ lọc' synchronized_filters: - add_new: 'Thêm bộ lọc đồng bộ hóa LDAP' + add_new: 'Thêm bộ lọc LDAP được đồng bộ hóa' singular: 'Bộ lọc đồng bộ hóa nhóm LDAP' - plural: 'Các bộ lọc đồng bộ hóa nhóm LDAP' + plural: 'Bộ lọc đồng bộ hóa nhóm LDAP' label_n_groups_found: one: "1 nhóm được tìm thấy bởi bộ lọc" - other: "%{count} nhóm được tìm thấy bởi bộ lọc" - zero: "Không có nhóm nào được tìm thấy bởi bộ lọc" + other: "%{count} nhóm được bộ lọc tìm thấy" + zero: "Không có nhóm nào được bộ lọc tìm thấy" destroy: - title: 'Xóa bộ lọc đồng bộ hóa %{name}' - confirmation: "Nếu bạn tiếp tục, bộ lọc đồng bộ hóa %{name} và tất cả các nhóm %{groups_count} được tạo ra qua bộ lọc này sẽ bị xóa." - removed_groups: "Cảnh báo: Điều này sẽ xóa các nhóm sau khỏi OpenProject và xóa khỏi tất cả các dự án!" - verification: "Nhập tên bộ lọc %{name} để xác nhận việc xóa." + title: 'Xóa bộ lọc đã đồng bộ hóa %{name}' + confirmation: "Nếu bạn tiếp tục, bộ lọc đã đồng bộ hóa %{name} và tất cả các nhóm %{groups_count} được tạo thông qua bộ lọc đó sẽ bị xóa." + removed_groups: "Cảnh báo: Thao tác này sẽ xóa các nhóm sau khỏi OpenProject và xóa nó khỏi tất cả các dự án!" + verification: "Nhập tên bộ lọc %{name} để xác minh việc xóa." form: group_name_attribute_text: 'Nhập thuộc tính của nhóm LDAP được sử dụng để đặt tên nhóm OpenProject.' filter_string_text: 'Nhập bộ lọc LDAP RFC4515 trả về các nhóm trong LDAP của bạn để đồng bộ hóa với OpenProject.' base_dn_text: > - Nhập base DN tìm kiếm để sử dụng cho bộ lọc này. Nó cần phải nằm dưới base DN của kết nối LDAP đã chọn. Để trống tùy chọn này để sử dụng lại base DN của kết nối + Nhập DN cơ sở tìm kiếm để sử dụng cho bộ lọc này. Nó phải ở dưới DN cơ sở của kết nối LDAP đã chọn. Để trống tùy chọn này để sử dụng lại DN cơ sở của kết nối synchronized_groups: - add_new: 'Thêm nhóm LDAP đồng bộ' + add_new: 'Thêm nhóm LDAP được đồng bộ hóa' destroy: - title: 'Xóa nhóm LDAP đồng bộ %{name}' - confirmation: "Nếu bạn tiếp tục, nhóm LDAP đồng bộ %{name} và tất cả %{users_count} người dùng đồng bộ qua nhóm này sẽ bị xóa." - info: "Lưu ý: Nhóm OpenProject chính nó và các thành viên được thêm vào ngoài đồng bộ hóa LDAP này sẽ không bị xóa." - verification: "Nhập tên nhóm %{name} để xác nhận việc xóa." + title: 'Xóa nhóm đã đồng bộ hóa %{name}' + confirmation: "Nếu bạn tiếp tục, nhóm được đồng bộ hóa %{name} và tất cả người dùng %{users_count} được đồng bộ hóa thông qua nhóm đó sẽ bị xóa." + info: "Lưu ý: Bản thân nhóm OpenProject và các thành viên được thêm vào bên ngoài quá trình đồng bộ hóa LDAP này sẽ không bị xóa." + verification: "Nhập tên nhóm %{name} để xác minh việc xóa." help_text_html: | Mô-đun này cho phép bạn thiết lập đồng bộ hóa giữa các nhóm LDAP và OpenProject. Nó phụ thuộc vào các nhóm LDAP cần sử dụng thuộc tính groupOfNames / memberOf để hoạt động với OpenProject.
    Các nhóm được đồng bộ hóa hàng giờ thông qua một công việc định kỳ. Xin vui lòng xem tài liệu của chúng tôi về chủ đề này. - no_results: 'Không tìm thấy nhóm đồng bộ nào.' + no_results: 'Không tìm thấy nhóm được đồng bộ hóa nào.' no_members: 'Nhóm này chưa có thành viên đồng bộ nào.' - plural: 'Các nhóm LDAP đã đồng bộ' - singular: 'Nhóm LDAP đã đồng bộ' + plural: 'Nhóm LDAP được đồng bộ hóa' + singular: 'Nhóm LDAP được đồng bộ hóa' form: - auth_source_text: 'Chọn kết nối LDAP nào nên được sử dụng.' + auth_source_text: 'Chọn kết nối LDAP nào sẽ được sử dụng.' sync_users_text: > - Nếu bạn kích hoạt tùy chọn này, các người dùng được tìm thấy cũng sẽ được tạo tự động trong OpenProject. Nếu không, chỉ các tài khoản hiện có trong OpenProject sẽ được thêm vào các nhóm. - dn_text: 'Nhập DN đầy đủ của nhóm trong LDAP' - group_text: 'Chọn một nhóm OpenProject hiện có mà các thành viên của nhóm LDAP sẽ được đồng bộ hóa với' + Nếu bạn bật tùy chọn này, những người dùng được tìm thấy cũng sẽ được tạo tự động trong OpenProject. Nếu không có nó, chỉ những tài khoản hiện có trong OpenProject mới được thêm vào nhóm. + dn_text: 'Nhập đầy đủ DN của nhóm vào LDAP' + group_text: 'Chọn nhóm OpenProject hiện có mà các thành viên của nhóm LDAP sẽ được đồng bộ hóa với' upsell: - description: 'Hãy tận dụng các nhóm LDAP đã đồng bộ để quản lý người dùng, thay đổi quyền hạn của họ và tạo điều kiện quản lý người dùng qua các nhóm.' + description: 'Tận dụng các nhóm LDAP được đồng bộ hóa để quản lý người dùng, thay đổi quyền của họ và tạo điều kiện thuận lợi cho việc quản lý người dùng giữa các nhóm.' diff --git a/modules/meeting/config/locales/crowdin/fr.yml b/modules/meeting/config/locales/crowdin/fr.yml index d8a67f2fd6a..dbec72e6360 100644 --- a/modules/meeting/config/locales/crowdin/fr.yml +++ b/modules/meeting/config/locales/crowdin/fr.yml @@ -236,15 +236,15 @@ fr: summary: "« %{title} » a été annulée par %{actor}." date_time: "Date/heure prévue" participant_added: - header: "Meeting '%{title}' - Participant added" - header_series: "Meeting series '%{title}' - Participant added" - summary: "%{actor} added %{participant} to the meeting '%{title}'" - summary_series: "%{actor} added %{participant} to the meeting series '%{title}'" + header: "Réunion « %{title} » - Participant ajouté" + header_series: "Série de réunions « %{title} » - Participant ajouté" + summary: "%{actor} a ajouté %{participant} à la réunion « %{title} »" + summary_series: "%{actor} a ajouté %{participant} à la série de réunions « %{title} »" participant_removed: - header: "Meeting '%{title}' - Participant removed" - header_series: "Meeting series '%{title}' - Participant removed" - summary: "%{actor} removed %{participant} from the meeting '%{title}'" - summary_series: "%{actor} removed %{participant} from the meeting series '%{title}'" + header: "Réunion « %{title} » - Participant supprimé" + header_series: "Série de réunions « %{title} » - Participant supprimé" + summary: "%{actor} a supprimé %{participant} de la réunion « %{title} »" + summary_series: "%{actor} a supprimé %{participant} de la série de réunions « %{title} »" ended: header_series: "Terminé : Série de rencontres '%{title}'" summary_series: "La série de réunions '%{title}' a été clôturée par %{actor}." @@ -544,7 +544,7 @@ fr: label_agenda_outcome_delete: "Supprimer le résultat" label_added_as_outcome: "Ajouté comme résultat" label_write_outcome: "Résultat de l'écriture" - label_existing_work_package: "Existing work package" + label_existing_work_package: "Lot de travaux existant" text_outcome_not_editable_anymore: "Ce résultat n'est plus modifiable." text_outcome_cannot_be_added: "Un résultat ne peut plus être ajouté." label_backlog_clear: "Effacer le backlog" @@ -611,9 +611,9 @@ fr: text_agenda_item_duplicate_in_next_meeting: "Êtes-vous sûr de vouloir ajouter une copie de ce point de l'ordre du jour à la prochaine réunion, sur %{date} à %{time}? Les résultats ne seront pas dupliqués." text_agenda_item_duplicated_in_next_meeting: "L'ordre du jour a été déplacé vers la prochaine réunion, le %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "Ce lot de travaux n'est pas encore inclus dans l'ordre du jour d'une prochaine réunion." - text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." - text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." - text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." + text_agenda_item_no_available_occurrence: "Toutes les occurrences à venir ont été annulées." + text_agenda_item_dialog_skipping_cancelled_one: "Note : abandon de l'occurrence annulée le %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Note : abandon de %{count} occurrences annulées." text_work_package_add_to_meeting_hint: 'Utilisez le bouton "Ajouter à la réunion" pour ajouter ce lot de travaux à une réunion à venir.' text_work_package_has_no_past_meeting_agenda_items: "Ce work package n'a pas été ajouté comme point à l'ordre du jour lors d'une réunion précédente." text_email_updates_muted: "Les mises à jour du calendrier par e-mail sont désactivées. Les participants ne recevront pas d'invitations actualisées par e-mail lorsque vous effectuerez des modifications." @@ -621,10 +621,10 @@ fr: my_account: access_tokens: token/ical_meeting: - blank_description: "You can create one using the button below." - blank_title: "No iCalendar meeting token" + blank_description: "Vous pouvez en créer une en utilisant le bouton ci-dessous." + blank_title: "Aucun jeton de réunion iCalendar" title: "iCalendar pour les réunions" - table_title: "iCalendar meeting tokens" + table_title: "Jetons de réunion iCalendar" text_hint: "Les jetons de réunion iCalendar permettent aux utilisateurs de s'abonner à toutes leurs réunions et d'afficher des informations actualisées sur les réunions dans des clients externes." disabled_text: "Les abonnements iCalendar ne sont pas activés par l'administrateur. Veuillez contacter votre administrateur pour utiliser cette fonctionnalité." add_button: "S'abonner au calendrier" diff --git a/modules/meeting/config/locales/crowdin/it.yml b/modules/meeting/config/locales/crowdin/it.yml index 523dc1299c7..d4c192bf605 100644 --- a/modules/meeting/config/locales/crowdin/it.yml +++ b/modules/meeting/config/locales/crowdin/it.yml @@ -236,15 +236,15 @@ it: summary: "\"%{title}\" è stata cancellata da %{actor}." date_time: "Data/ora programmata" participant_added: - header: "Meeting '%{title}' - Participant added" - header_series: "Meeting series '%{title}' - Participant added" - summary: "%{actor} added %{participant} to the meeting '%{title}'" - summary_series: "%{actor} added %{participant} to the meeting series '%{title}'" + header: "Riunione '%{title}' - Partecipante aggiunto" + header_series: "Serie di riunioni '%{title}' - Partecipante aggiunto" + summary: "%{actor} ha aggiunto %{participant} alla riunione '%{title}'" + summary_series: "%{actor} ha aggiunto %{participant} alla serie di riunioni '%{title}'" participant_removed: - header: "Meeting '%{title}' - Participant removed" - header_series: "Meeting series '%{title}' - Participant removed" - summary: "%{actor} removed %{participant} from the meeting '%{title}'" - summary_series: "%{actor} removed %{participant} from the meeting series '%{title}'" + header: "Riunione '%{title}' - Partecipante rimosso" + header_series: "Serie di riunioni '%{title}' - Partecipante rimosso" + summary: "%{actor} ha rimosso %{participant} dalla riunione '%{title}'" + summary_series: "%{actor} ha rimosso %{participant} dalla serie di riunioni '%{title}'" ended: header_series: "Terminata: serie di riunioni \"%{title}\"" summary_series: "La serie di riunioni '%{title}' è stata conclusa da %{actor}." @@ -542,9 +542,9 @@ it: label_agenda_outcome_actions: "Azioni di risultato dell'ordine del giorno" label_agenda_outcome_edit: "Modifica risultato" label_agenda_outcome_delete: "Rimuovi risultato" - label_added_as_outcome: "Added as outcome" - label_write_outcome: "Write outcome" - label_existing_work_package: "Existing work package" + label_added_as_outcome: "Aggiunto come risultato" + label_write_outcome: "Scrivi risultato" + label_existing_work_package: "Macro-attività esistente" text_outcome_not_editable_anymore: "Questo risultato non è più modificabile." text_outcome_cannot_be_added: "Non è più possibile aggiungere un risultato." label_backlog_clear: "Ripulisci il backlog" @@ -611,9 +611,9 @@ it: text_agenda_item_duplicate_in_next_meeting: "Vuoi davvero aggiungere una copia di questo punto all'ordine del giorno della prossima riunione, il giorno %{date} alle ore %{time}? I risultati non verranno duplicati." text_agenda_item_duplicated_in_next_meeting: "Punto all'ordine del giorno duplicato nella prossima riunione, il giorno %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "Questa macro-attività non è ancora programmata in un prossimo ordine del giorno di riunione." - text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." - text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." - text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." + text_agenda_item_no_available_occurrence: "Tutte le prossime occorrenze sono state annullate." + text_agenda_item_dialog_skipping_cancelled_one: "Nota: verrà saltata l'occorrenza annullata il giorno %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Nota: vengono saltate %{count} occorrenze annullate." text_work_package_add_to_meeting_hint: 'Utilizzare il pulsante "Aggiungi alla riunione" per aggiungere questa macro-attività a una riunione imminente.' text_work_package_has_no_past_meeting_agenda_items: "Questa macro-attività non è stata aggiunta come punto all'ordine del giorno in una riunione precedente." text_email_updates_muted: "Gli aggiornamenti del calendario via email sono disattivati. I partecipanti non riceveranno inviti aggiornati via email quando apporti modifiche." @@ -621,10 +621,10 @@ it: my_account: access_tokens: token/ical_meeting: - blank_description: "You can create one using the button below." - blank_title: "No iCalendar meeting token" + blank_description: "Puoi crearne uno utilizzando il pulsante qui sotto." + blank_title: "Nessun token di riunione iCalendar" title: "iCalendar per le riunioni" - table_title: "iCalendar meeting tokens" + table_title: "Token di riunione iCalendar" text_hint: "I token iCalendar delle riunioni consentono agli utenti di iscriversi a tutte le proprie riunioni e di visualizzare le informazioni aggiornate in client esterni." disabled_text: "Le iscrizioni alle riunioni iCalendar non sono abilitate dall'amministratore. Contatta il tuo amministratore per utilizzare questa funzione." add_button: "Iscriviti al calendario" diff --git a/modules/meeting/config/locales/crowdin/js-vi.yml b/modules/meeting/config/locales/crowdin/js-vi.yml index bc7d11e5d19..3c15590fd21 100644 --- a/modules/meeting/config/locales/crowdin/js-vi.yml +++ b/modules/meeting/config/locales/crowdin/js-vi.yml @@ -21,7 +21,7 @@ #++ vi: js: - label_meetings: 'Các cuộc họp' + label_meetings: 'các cuộc họp' work_packages: tabs: - meetings: 'Cuộc họp' + meetings: 'các cuộc họp' diff --git a/modules/meeting/config/locales/crowdin/ko.yml b/modules/meeting/config/locales/crowdin/ko.yml index 0e288e69b36..4e2f0611c3e 100644 --- a/modules/meeting/config/locales/crowdin/ko.yml +++ b/modules/meeting/config/locales/crowdin/ko.yml @@ -604,9 +604,9 @@ ko: text_agenda_item_duplicate_in_next_meeting: "%{date}, %{time}의 다음 미팅에 이 의제의 사본을 추가하시겠습니까? 결과는 복제되지 않습니다." text_agenda_item_duplicated_in_next_meeting: "의제 항목이 %{date}의 다음 미팅에서 복제되었습니다" text_work_package_has_no_upcoming_meeting_agenda_items: "이 작업 패키지는 향후 미팅 의제에서 아직 예약되지 않았습니다." - text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." - text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." - text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." + text_agenda_item_no_available_occurrence: "모든 향후 항목이 취소되었습니다." + text_agenda_item_dialog_skipping_cancelled_one: "참고: %{date}에 취소된 항목을 건너뜁니다." + text_agenda_item_dialog_skipping_cancelled_many: "참고: 취소된 항목 %{count}개를 건너뜁니다." text_work_package_add_to_meeting_hint: '이 작업 패키지를 향후 미팅에 추가하려면 "미팅에 추가" 버튼을 사용하세요.' text_work_package_has_no_past_meeting_agenda_items: "이 작업 패키지는 지난 미팅에서 의제 항목으로 추가되지 않았습니다." text_email_updates_muted: "이메일 캘린더 업데이트가 음소거되었습니다. 변경해도 이메일을 통해 업데이트된 초대장이 참가자에게 전송되지 않습니다." @@ -614,10 +614,10 @@ ko: my_account: access_tokens: token/ical_meeting: - blank_description: "You can create one using the button below." - blank_title: "No iCalendar meeting token" + blank_description: "아래 버튼을 사용하여 만들 수 있습니다." + blank_title: "iCalendar 미팅 토큰 없음" title: "미팅용 iCalendar" - table_title: "iCalendar meeting tokens" + table_title: "iCalendar 미팅 토큰" text_hint: "iCalendar 미팅 토큰을 통해 사용자는 모든 미팅을 구독하고 외부 클라이언트에서 최신 미팅 정보를 볼 수 있습니다." disabled_text: "관리자가 iCalendar 미팅 구독을 활성화하지 않았습니다. 이 기능을 사용하려면 관리자에게 문의하세요." add_button: "캘린더 구독" diff --git a/modules/meeting/config/locales/crowdin/pl.yml b/modules/meeting/config/locales/crowdin/pl.yml index b545ec89321..4d66b874228 100644 --- a/modules/meeting/config/locales/crowdin/pl.yml +++ b/modules/meeting/config/locales/crowdin/pl.yml @@ -246,15 +246,15 @@ pl: summary: "„%{title}” anulował użytkownik %{actor}." date_time: "Zaplanowana data/godzina" participant_added: - header: "Meeting '%{title}' - Participant added" - header_series: "Meeting series '%{title}' - Participant added" - summary: "%{actor} added %{participant} to the meeting '%{title}'" - summary_series: "%{actor} added %{participant} to the meeting series '%{title}'" + header: "Spotkanie „%{title}” — dodano uczestnika" + header_series: "Seria spotkań „%{title}” — dodano uczestnika" + summary: "Użytkownik %{actor} dodał uczestnika %{participant} do spotkania „%{title}”" + summary_series: "Użytkownik %{actor} dodał uczestnika %{participant} do serii spotkań „%{title}”" participant_removed: - header: "Meeting '%{title}' - Participant removed" - header_series: "Meeting series '%{title}' - Participant removed" - summary: "%{actor} removed %{participant} from the meeting '%{title}'" - summary_series: "%{actor} removed %{participant} from the meeting series '%{title}'" + header: "Spotkanie „%{title}” — usunięto uczestnika" + header_series: "Seria spotkań „%{title}” — usunięto uczestnika" + summary: "Użytkownik %{actor} usunął uczestnika %{participant} ze spotkania „%{title}”" + summary_series: "Użytkownik %{actor} usunął uczestnika %{participant} z serii spotkań „%{title}”" ended: header_series: "Zakończono: serię spotkań „%{title}”" summary_series: "Seria spotkań „%{title}” została zakończona przez użytkownika %{actor}." @@ -556,9 +556,9 @@ pl: label_agenda_outcome_actions: "Działania wyniku planu spotkania" label_agenda_outcome_edit: "Edytuj wynik" label_agenda_outcome_delete: "Usuń wynik" - label_added_as_outcome: "Added as outcome" - label_write_outcome: "Write outcome" - label_existing_work_package: "Existing work package" + label_added_as_outcome: "Dodano jako wynik" + label_write_outcome: "Zapisz wynik" + label_existing_work_package: "Istniejący pakiet roboczy" text_outcome_not_editable_anymore: "Tego wyniku nie można już edytować." text_outcome_cannot_be_added: "Nie można już dodać wyniku." label_backlog_clear: "Wyczyść backlog" @@ -625,9 +625,9 @@ pl: text_agenda_item_duplicate_in_next_meeting: "Czy na pewno chcesz dodać kopię tego punktu planu spotkania do następnego spotkania w dniu %{date} o godzinie %{time}? Wyniki nie będą duplikowane." text_agenda_item_duplicated_in_next_meeting: "Pozycję planu spotkania zduplikowano w następnym spotkaniu w dniu %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "Ten pakiet roboczy nie został jeszcze zaplanowany w planie nadchodzącego spotkania." - text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." - text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." - text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." + text_agenda_item_no_available_occurrence: "Wszystkie nadchodzące wystąpienia zostały anulowane." + text_agenda_item_dialog_skipping_cancelled_one: "Uwaga: pomijanie anulowanego wystąpienia w dniu %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Uwaga: pomijanie %{count} anulowanych wystąpień." text_work_package_add_to_meeting_hint: 'Użyj przycisku „Dodaj do spotkania”, aby dodać ten pakiet roboczy do nadchodzącego spotkania.' text_work_package_has_no_past_meeting_agenda_items: "Ten pakiet roboczy nie został dodany jako pozycja planu spotkania w poprzednim spotkaniu." text_email_updates_muted: "Wysyłanie aktualizacji kalendarza pocztą elektroniczną jest wyciszone. Po wprowadzeniu zmian uczestnicy nie będą otrzymywać zaktualizowanych zaproszeń pocztą elektroniczną." @@ -635,10 +635,10 @@ pl: my_account: access_tokens: token/ical_meeting: - blank_description: "You can create one using the button below." - blank_title: "No iCalendar meeting token" + blank_description: "Możesz go utworzyć za pomocą poniższego przycisku." + blank_title: "Brak tokenu spotkania iCalendar" title: "iCalendar dla spotkań" - table_title: "iCalendar meeting tokens" + table_title: "Tokeny spotkań iCalendar" text_hint: "Tokeny spotkań aplikacji iCalendar umożliwiają użytkownikom subskrybowanie wszystkich ich spotkań i wyświetlanie aktualnych informacji o spotkaniach w klientach zewnętrznych." disabled_text: "Subskrypcje spotkań w iCalendar nie zostały włączone przez administratora. Aby użyć tej funkcji, skontaktuj się z administratorem." add_button: "Subskrybuj kalendarz" diff --git a/modules/meeting/config/locales/crowdin/uk.yml b/modules/meeting/config/locales/crowdin/uk.yml index 0610bc43c91..11181409f13 100644 --- a/modules/meeting/config/locales/crowdin/uk.yml +++ b/modules/meeting/config/locales/crowdin/uk.yml @@ -635,10 +635,10 @@ uk: my_account: access_tokens: token/ical_meeting: - blank_description: "Ви можете створити нараду, натиснувши кнопку нижче." - blank_title: "Немає маркера наради в iCalendar" + blank_description: "Щоб створити нараду, натисніть кнопку нижче." + blank_title: "Немає маркера наради для iCalendar" title: "iCalendar для нарад" - table_title: "Маркери нарад в iCalendar" + table_title: "Маркери нарад для iCalendar" text_hint: "Маркери iCalendar для нарад дають змогу користувачам підписуватися на всі свої наради й переглядати актуальну інформацію про них у зовнішніх клієнтах." disabled_text: "Підписки на наради iCalendar не активував адміністратор. Зв’яжіться з ним, щоб використовувати цю функцію." add_button: "Підписатися на календар" diff --git a/modules/meeting/config/locales/crowdin/vi.seeders.yml b/modules/meeting/config/locales/crowdin/vi.seeders.yml index 104bab460a9..74f9305fd0a 100644 --- a/modules/meeting/config/locales/crowdin/vi.seeders.yml +++ b/modules/meeting/config/locales/crowdin/vi.seeders.yml @@ -9,21 +9,21 @@ vi: demo-project: meeting_series: item_0: - title: Mỗi tuần + title: hàng tuần meeting_agenda_items: item_0: - title: Tin tốt + title: tin tốt item_1: title: Cập nhật từ nhóm phát triển item_2: title: Cập nhật từ nhóm sản phẩm item_3: - title: Cập nhật từ nhóm marketing + title: Cập nhật từ nhóm tiếp thị item_5: - title: Cập nhật từ nhóm bán hàng + title: Cập nhật từ đội ngũ bán hàng item_6: - title: Xem xét các mục tiêu hàng quý + title: Đánh giá mục tiêu hàng quý item_7: title: Phản hồi về giá trị cốt lõi item_8: - title: Các chủ đề chung + title: Chủ đề chung diff --git a/modules/meeting/config/locales/crowdin/vi.yml b/modules/meeting/config/locales/crowdin/vi.yml index 9eabce7a5fc..fbca599b752 100644 --- a/modules/meeting/config/locales/crowdin/vi.yml +++ b/modules/meeting/config/locales/crowdin/vi.yml @@ -22,154 +22,154 @@ #English strings go here for Rails i18n vi: plugin_openproject_meeting: - name: "Cuộc họp OpenProject" + name: "Cuộc họp dự án mở" description: >- - Mô-đun này thêm các chức năng hỗ trợ cuộc họp dự án vào OpenProject. Cuộc họp có thể được lên lịch bằng cách chọn những người được mời từ cùng một dự án để tham gia cuộc họp. Một chương trình nghị sự có thể được tạo và gửi cho những người được mời. Sau cuộc họp, người tham dự có thể được chọn và biên bản có thể được tạo dựa trên chương trình nghị sự. Cuối cùng, biên bản có thể được gửi đến tất cả người tham dự và những người được mời. + Mô-đun này bổ sung thêm các chức năng hỗ trợ các cuộc họp dự án cho OpenProject. Các cuộc họp có thể được lên lịch bằng cách chọn những người được mời từ cùng một dự án để tham gia cuộc họp. Một chương trình nghị sự có thể được tạo và gửi cho những người được mời. Sau cuộc họp, người tham dự có thể được chọn và biên bản có thể được tạo dựa trên chương trình nghị sự. Cuối cùng, biên bản có thể được gửi đến tất cả những người tham dự và những người được mời. activerecord: attributes: meeting: type: "Loại cuộc họp" location: "Địa điểm" - duration: "Thời gian" + duration: "thời lượng" notes: "Ghi chú" participants: "Người tham gia" participant: other: "%{count} Người tham gia" participants_attended: "Người dự khán" participants_invited: "Khách mời" - project: "Dự án" - start_date: "Ngày" + project: "dự án" + start_date: "ngày" start_time: "Thời gian bắt đầu" start_time_hour: "Thời gian bắt đầu" meeting_agenda_item: - title: "Tiêu đề" - author: "Tác giả" - duration_in_minutes: "Thời gian" + title: "tiêu đề" + author: "tác giả" + duration_in_minutes: "thời lượng" description: "Ghi chú" presenter: "Người trình bày" meeting_section: - title: "Tiêu đề" + title: "tiêu đề" recurring_meeting: - frequency: "Tần suất" - interval: "Khoảng thời gian" + frequency: "tần số" + interval: "khoảng thời gian" start_date: "Bắt đầu vào" start_time: "Thời gian bắt đầu" start_time_hour: "Thời gian bắt đầu" - end_after: "Kết thúc chuỗi họp" - end_date: "End date" - iterations: "Số lần xuất hiện" + end_after: "Chuỗi cuộc họp kết thúc" + end_date: "Ngày kết thúc" + iterations: "lần xuất hiện" meeting_participant: invited: "Đã mời" - attended: "Đã tham dự" - participation_status: "Participation status" + attended: "đã tham dự" + participation_status: "Trạng thái tham gia" errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + invalid_user: "%{name} không phải là người tham gia hợp lệ." recurring_meeting: must_cover_existing_meetings: - one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." - other: "There are %{count} open meetings in the series that are not covered by the new schedule. Adjust the schedule to include all existing meetings." + one: "Có một cuộc họp mở trong chuỗi không có trong lịch trình mới. Điều chỉnh lịch trình để bao gồm tất cả các cuộc họp hiện có." + other: "Có %{count} cuộc họp mở trong chuỗi không nằm trong lịch trình mới. Điều chỉnh lịch trình để bao gồm tất cả các cuộc họp hiện có." format: "%{message}" messages: invalid_time_format: "không phải giờ hợp lệ. Yêu cầu định dạng: HH:MM" models: - recurring_meeting: "Họp định kỳ" - meeting: "Họp đột xuất" + recurring_meeting: "Cuộc họp định kỳ" + meeting: "Cuộc gặp gỡ một lần" meeting_agenda_item: "Mục chương trình nghị sự" meeting_agenda: "Các ý chính" - meeting_section: "Phần" + meeting_section: "phần" token/ical_meeting: - other: "iCal Meeting subscription" + other: "Đăng ký cuộc họp iCal" activity: filter: - meeting: "Các cuộc họp" + meeting: "các cuộc họp" item: meeting_agenda_item: duration: added: "đặt thành %{value}" - added_html: "đặt thành %{value}" - removed: "đã gỡ bỏ" - updated: "thay đổi từ %{old_value} thành %{value}" - updated_html: "thay đổi từ %{old_value} thành %{value}" + added_html: "được đặt thành %{value}" + removed: "đã xóa" + updated: "đã thay đổi từ %{old_value} thành %{value}" + updated_html: "đã thay đổi từ %{old_value}_ thành %{value}_" position: - updated: "đã sắp xếp lại" + updated: "sắp xếp lại" work_package: - updated: "thay đổi từ %{old_value} thành %{value}" - updated_html: "thay đổi từ %{old_value} thành %{value}" + updated: "đã thay đổi từ %{old_value} thành %{value}" + updated_html: "đã thay đổi từ %{old_value}_ thành %{value}_" description_attended: "Đã tham dự" events: - meeting: Cuộc họp đã được chỉnh sửa - meeting_agenda: Chương trình nghị sự cuộc họp đã được chỉnh sửa - meeting_agenda_closed: Chương trình nghị sự cuộc họp đã được đóng - meeting_agenda_opened: Chương trình nghị sự cuộc họp đã được mở - meeting_minutes: Biên bản cuộc họp đã được chỉnh sửa - meeting_minutes_created: Biên bản cuộc họp đã được tạo - error_notification: "Lỗi gửi thông báo" + meeting: Đã chỉnh sửa cuộc họp + meeting_agenda: Chương trình họp đã được chỉnh sửa + meeting_agenda_closed: Chương trình họp đã kết thúc + meeting_agenda_opened: Đã mở chương trình họp + meeting_minutes: Biên bản họp đã được chỉnh sửa + meeting_minutes_created: Đã tạo biên bản cuộc họp + error_notification: "Không thể gửi thông báo." error_notification_with_errors: "Không thể gửi thông báo. Những người không thể nhận được thông báo: %{recipients}" label_meeting: "Cuộc họp" label_meeting_plural: "Những cuộc họp" label_meeting_new: "Cuộc họp mới" - label_meeting_new_dynamic: "Cuộc họp đột xuất mới" + label_meeting_new_dynamic: "Cuộc họp một lần mới" label_meeting_new_recurring: "Cuộc họp định kỳ mới" label_meeting_create: "Tạo cuộc họp" - label_meeting_duplicate: "Duplicate meeting" + label_meeting_duplicate: "Cuộc họp trùng lặp" label_meeting_edit: "Chỉnh sửa cuộc họp" - label_meeting_agenda: "Chương trình họp" - label_meeting_minutes: "Biên bản cuộc họp" + label_meeting_agenda: "chương trình họp" + label_meeting_minutes: "phút" label_meeting_close: "Đóng" label_meeting_open: "Mở" - label_meeting_index_delete: "Xoá" + label_meeting_index_delete: "xóa" 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_agenda_close: "Đóng chương trình làm việc để bắt đầu Biên bản" label_meeting_date_time: "Ngày/Giờ" label_meeting_date_and_time: "Ngày và giờ" - label_meeting_diff: "Sự khác biệt" - label_meeting_send_updates: "Send email calendar invite and updates" - label_meeting_send_updates_caption: "Send out email invites to all participants of this meeting" - label_recurring_meeting: "Họp định kỳ" - label_recurring_meeting_part_of: "Phần của chuỗi cuộc họp" + label_meeting_diff: "khác biệt" + label_meeting_send_updates: "Gửi lời mời và cập nhật lịch qua email" + label_meeting_send_updates_caption: "Gửi lời mời qua email tới tất cả những người tham gia cuộc họp này" + label_recurring_meeting: "Cuộc họp định kỳ" + label_recurring_meeting_part_of: "Một phần của chuỗi cuộc họp" label_recurring_meeting_new: "Cuộc họp định kỳ mới" - label_recurring_meeting_plural: "Họp định kỳ" - label_template: "Mẫu" - label_recurring_meeting_view: "Xem chuỗi các cuộc họp" - label_recurring_meeting_create: "Mở" - label_recurring_meeting_duplicate: "Duplicate as a one-time meeting" - label_recurring_meeting_cancel: "Hủy lần này" - label_recurring_meeting_delete: "Xóa lần họp" - label_recurring_meeting_restore: "Khôi phục lại lần họp này" - label_recurring_meeting_schedule: "Lên lịch" - label_recurring_meeting_next_occurrence: "Lần họp tới" - label_recurring_meeting_no_end_date: "Còn nhiều lịch họp (%{schedule})." + label_recurring_meeting_plural: "Cuộc họp định kỳ" + label_template: "mẫu" + label_recurring_meeting_view: "Xem chuỗi cuộc họp" + label_recurring_meeting_create: "mở" + label_recurring_meeting_duplicate: "Nhân bản dưới dạng cuộc họp một lần" + label_recurring_meeting_cancel: "Hủy lần xuất hiện này" + label_recurring_meeting_delete: "Xóa lần xuất hiện" + label_recurring_meeting_restore: "Khôi phục sự cố này" + label_recurring_meeting_schedule: "lịch trình" + label_recurring_meeting_next_occurrence: "Lần xuất hiện tiếp theo" + label_recurring_meeting_no_end_date: "Có nhiều cuộc họp theo lịch trình hơn (%{schedule})." label_recurring_meeting_more: - other: "Còn %{count} cuộc họp được lên lịch nữa (%{schedule})." + other: "Có thêm %{count} cuộc họp đã lên lịch (%{schedule})." label_recurring_meeting_more_past: - other: "Còn %{count} cuộc họp trước đó." - label_recurring_meeting_show_more: "Xem thêm" - label_recurring_meeting_series_create: "Tạo chuỗi các cuộc họp" - label_recurring_meeting_series_edit: "Sửa chuỗi các cuộc họp" - label_recurring_meeting_series_delete: "Xóa chuỗi các cuộc họp" - label_recurring_meeting_series_end: "End meeting series" - label_recurring_meeting_series_end_now: "End series now" + other: "Có thêm %{count} cuộc họp trước đây." + label_recurring_meeting_show_more: "Hiển thị thêm" + label_recurring_meeting_series_create: "Tạo chuỗi cuộc họp" + label_recurring_meeting_series_edit: "Chỉnh sửa chuỗi cuộc họp" + label_recurring_meeting_series_delete: "Xóa chuỗi cuộc họp" + label_recurring_meeting_series_end: "Kết thúc chuỗi cuộc họp" + label_recurring_meeting_series_end_now: "Kết thúc loạt bài ngay bây giờ" label_meeting_more: - other: "There are %{count} more meetings." + other: "Còn %{count} cuộc họp nữa." label_my_meetings: "Cuộc họp của tôi" label_all_meetings: "Tất cả các cuộc họp" - label_upcoming_meetings: "Các cuộc họp sắp tới" - label_past_meetings: "Các cuộc họp đã qua" + label_upcoming_meetings: "Cuộc họp sắp tới" + label_past_meetings: "Các cuộc họp trước đây" label_upcoming_meetings_short: "Sắp tới" - label_past_meetings_short: "Đã qua" - label_involvement: "Tham gia" + label_past_meetings_short: "quá khứ" + label_involvement: "Sự tham gia" label_invitations: "Lời mời" - label_invited_user: "Người dùng đã mời" - label_past_invitations: "Lời mời đã qua" - label_attended: "Đã tham dự" + label_invited_user: "Người dùng được mời" + label_past_invitations: "Lời mời trước đây" + label_attended: "đã tham dự" label_attended_user: "Người dùng đã tham dự" - label_created_by_me: "Tạo bởi tôi" + label_created_by_me: "Được tạo bởi tôi" label_notify: "Gửi để xem xét" label_icalendar: "Gửi iCalendar" label_icalendar_download: "Tải xuống sự kiện iCalendar" - label_view_meeting_series: "Xem chuỗi các cuộc họp" + label_view_meeting_series: "Xem chuỗi cuộc họp" label_meeting_series: "Chuỗi cuộc họp" label_version: "0886055830 " label_time_zone: "Múi giờ" @@ -178,463 +178,463 @@ vi: meeting: participants: label: - participants: "Người tham gia" - attended: "Đã tham dự" - mark_as_attended: "Mark as attended" - mark_all_as_attended: "Mark all as attended" - remove_participant: "Remove participant" + participants: "người tham gia" + attended: "đã tham dự" + mark_as_attended: "Đánh dấu là đã tham dự" + mark_all_as_attended: "Đánh dấu tất cả là đã tham dự" + remove_participant: "Xóa người tham gia" manage_participants: "Quản lý người tham gia" no_participants: "Không có người tham gia" show_all: "Hiển thị tất cả" text: - template: "Những người này sẽ được mời tham gia tự động tất cả các cuộc họp được tạo trong tương lai." - manage_participants: "Search for and add project members as participants to this meeting." - search_for_members: "Search for project members" + template: "Những người tham gia này sẽ tự động được mời tham gia tất cả các cuộc họp trong tương lai khi chúng được tạo." + manage_participants: "Tìm kiếm và thêm thành viên dự án làm người tham gia cuộc họp này." + search_for_members: "Tìm kiếm thành viên dự án" blankslate: heading: "Không có người tham gia" - description: "There are no participants yet." + description: "Chưa có người tham gia." attachments: - template: "Những tệp đính kèm này sẽ được bổ sung trong tất cả các cuộc họp tương lai trong chuỗi." - text: "Các tệp đính kèm có sẵn cho tất cả người tham gia cuộc họp. Bạn cũng có thể kéo và thả các tệp này vào ghi chú mục chương trình nghị sự." + template: "Các tệp đính kèm này sẽ được đưa vào tất cả các cuộc họp trong tương lai trong chuỗi." + text: "Các tệp đính kèm có sẵn cho tất cả những người tham gia cuộc họp. Bạn cũng có thể kéo và thả chúng vào ghi chú mục chương trình nghị sự." copy: title: "Sao chép cuộc họp: %{title}" attachments: "Sao chép tệp đính kèm" - attachments_text: "Sao chép tất cả các tệp đính kèm vào cuộc họp mới" - agenda: "Sao chép chương trình nghị sự" - agenda_items: "Chép mục chương trình nghị sự" + attachments_text: "Sao chép tất cả các tập tin đính kèm vào cuộc họp mới" + agenda: "Sao chép chương trình làm việc" + agenda_items: "Sao chép các mục chương trình nghị sự" agenda_text: "Sao chép chương trình nghị sự của cuộc họp cũ" - participants: "Chép danh sách người tham gia" + participants: "Sao chép danh sách người tham gia" to_clipboard: "Sao chép liên kết vào clipboard" email: - send_emails: "Gửi email cho người tham gia" + send_emails: "Người tham gia email" send_invitation_emails: > - Gửi lời mời qua email ngay lập tức đến những người tham gia được chọn ở trên. Bạn cũng có thể thực hiện thủ công bất kỳ lúc nào sau đó. - send_invitation_emails_structured: "Gửi lời mời qua email ngay lập tức đến tất cả người tham gia. Bạn cũng có thể thực hiện thủ công bất kỳ lúc nào sau đó." - open_meeting_link: "Mở cuộc họp" - open_my_meetings_link: "Đến buổi họp của tôi" + Gửi lời mời email ngay lập tức đến những người tham gia được chọn ở trên. Bạn cũng có thể thực hiện việc này một cách thủ công bất kỳ lúc nào sau đó. + send_invitation_emails_structured: "Gửi lời mời qua email ngay lập tức tới tất cả người tham gia. Bạn cũng có thể thực hiện việc này một cách thủ công bất kỳ lúc nào sau đó." + open_meeting_link: "Cuộc họp mở" + open_my_meetings_link: "Đi tới cuộc họp của tôi" series: - title: "[%{project_name}] Meeting series '%{title}'" - summary: "%{actor} has invited you to a new meeting series '%{title}'" + title: "[%{project_name}] Chuỗi hội thảo '%{title}'" + summary: "%{actor} đã mời bạn tham gia chuỗi cuộc họp mới '%{title}'" series_updated: - title: "[%{project_name}] Meeting series '%{title}' has been updated" - summary: "Meeting series '%{title}' has been updated by %{actor}" - old_schedule: "Old schedule" - new_schedule: "New schedule" + title: "[%{project_name}] Chuỗi sự kiện '%{title}' đã được cập nhật" + summary: "Chuỗi cuộc họp '%{title}' đã được cập nhật bởi %{actor}" + old_schedule: "Lịch trình cũ" + new_schedule: "Lịch trình mới" invited: - summary: "%{actor} has sent you an invitation for the meeting '%{title}'" + summary: "%{actor} đã gửi cho bạn lời mời họp '%{title}'" cancelled: - header: "Cancelled: Meeting '%{title}'" - header_occurrence: "Cancelled: Meeting occurrence '%{title}'" - header_series: "Cancelled: Meeting series '%{title}'" - summary_occurrence: "An occurrence of '%{title}' has been cancelled by %{actor}." - summary_series: "Meeting series '%{title}' has been cancelled by %{actor}." - summary: "'%{title}' has been cancelled by %{actor}." - date_time: "Scheduled date/time" + header: "Đã hủy: Cuộc họp '%{title}'" + header_occurrence: "Đã hủy: Cuộc họp diễn ra '%{title}'" + header_series: "Đã hủy: Chuỗi cuộc họp '%{title}'" + summary_occurrence: "Sự xuất hiện của '%{title}' đã bị hủy bởi %{actor}." + summary_series: "Chuỗi cuộc họp '%{title}' đã bị hủy bởi %{actor}." + summary: "'%{title}' đã bị hủy bởi %{actor}." + date_time: "Ngày/giờ dự kiến" participant_added: - header: "Meeting '%{title}' - Participant added" - header_series: "Meeting series '%{title}' - Participant added" - summary: "%{actor} added %{participant} to the meeting '%{title}'" - summary_series: "%{actor} added %{participant} to the meeting series '%{title}'" + header: "Họp '%{title}' - Thành viên tham gia đã được thêm vào" + header_series: "Chuỗi hội thảo '%{title}' - Thêm người tham gia" + summary: "%{actor} Thêm %{participant} vào cuộc họp '%{title}'" + summary_series: "%{actor} Thêm \" %{participant} \" vào chuỗi sự kiện \"%{title}\"" participant_removed: - header: "Meeting '%{title}' - Participant removed" - header_series: "Meeting series '%{title}' - Participant removed" - summary: "%{actor} removed %{participant} from the meeting '%{title}'" - summary_series: "%{actor} removed %{participant} from the meeting series '%{title}'" + header: "Cuộc họp '%{title}' - Người tham gia đã bị loại bỏ" + header_series: "Chuỗi hội thảo '%{title}' - Người tham gia đã bị loại bỏ" + summary: "%{actor} Đã loại bỏ ' %{participant} ' khỏi cuộc họp '%{title}'" + summary_series: "%{actor} Đã loại bỏ \" %{participant} \" khỏi chuỗi cuộc họp \"%{title}\"" ended: - header_series: "Ended: Meeting series '%{title}'" - summary_series: "Meeting series '%{title}' has been ended by %{actor}." + header_series: "Đã kết thúc: Chuỗi cuộc họp '%{title}'" + summary_series: "Chuỗi cuộc họp '%{title}' đã kết thúc bởi %{actor}." updated: - header: "Meeting '%{title}' has been updated" - summary: "Meeting '%{title}' has been updated by %{actor}" - body: "The meeting '%{title}' has been updated by %{actor}." - old_title: "Old title" - new_title: "New title" + header: "Cuộc họp '%{title}' đã được cập nhật" + summary: "Cuộc họp '%{title}' đã được cập nhật bởi %{actor}" + body: "Cuộc họp '%{title}' đã được cập nhật bởi %{actor}." + old_title: "Tiêu đề cũ" + new_title: "Tiêu đề mới" old_date_time: "Ngày/giờ cũ" new_date_time: "Ngày/giờ mới" - old_location: "Old location" - new_location: "New location" - label_mail_all_participants: "Send email invite to participants" + old_location: "Vị trí cũ" + new_location: "Vị trí mới" + label_mail_all_participants: "Gửi email mời đến người tham gia" types: one_time: "Một lần" recurring: "Định kỳ" - recurring_text: "Tạo chuỗi họp với mẫu động cho mỗi lần họp." - structured_text: "Tổ chức cuộc họp của bạn dưới dạng danh sách động các mục chương trình nghị sự, có thể liên kết chúng với một gói công việc." - structured_text_copy: "Sao chép một cuộc họp hiện tại sẽ không sao chép các mục chương trình nghị sự liên quan, chỉ sao chép các chi tiết" + recurring_text: "Tạo chuỗi cuộc họp với mẫu động cho mỗi lần diễn ra." + structured_text: "Tổ chức cuộc họp của bạn dưới dạng danh sách động gồm các mục trong chương trình nghị sự, có thể tùy ý liên kết chúng với một gói công việc." + structured_text_copy: "Sao chép cuộc họp hiện sẽ không sao chép các mục trong chương trình cuộc họp liên quan mà chỉ sao chép chi tiết" copied: "Sao chép từ Cuộc họp #%{id}" delete_dialog: one_time: title: "Xóa cuộc họp" - heading: "Delete this meeting?" + heading: "Xóa cuộc họp này?" confirmation_message_html: > - This action is not reversible. Please proceed with caution. + Hành động này không thể đảo ngược. Hãy tiến hành thận trọng. occurrence: - title: "Cancel meeting occurrence" - heading: "Cancel this meeting occurrence?" + title: "Hủy cuộc họp diễn ra" + heading: "Hủy cuộc họp này diễn ra?" confirmation_message_html: > - Any meeting information not in the template will be lost. Do you want to continue? - confirm_button: "Cancel occurrence" + Mọi thông tin cuộc họp không có trong mẫu sẽ bị mất. Bạn có muốn tiếp tục không? + confirm_button: "Hủy lần xuất hiện" blankslate: - title: "There are no meetings to display" - desc: "You can create a new meeting or change filter criteria" - label_export_pdf: "Export PDF" + title: "Không có cuộc họp nào để hiển thị" + desc: "Bạn có thể tạo cuộc họp mới hoặc thay đổi tiêu chí lọc" + label_export_pdf: "Xuất PDF" export: - your_meeting_export: "Meeting is being exported" + your_meeting_export: "Cuộc họp đang được xuất" minutes: - footer_page_numbers: "P. %{current_page} of %{total_pages}" - author: "Tác giả" - date: "Ngày" - time: "Thời gian" - location: "Địa điểm" - title: "Biên bản cuộc họp" + footer_page_numbers: "P. %{current_page} của %{total_pages}" + author: "tác giả" + date: "ngày" + time: "thời gian" + location: "vị trí" + title: "phút" export_pdf_dialog: - title: Export PDF - description: Generate a printable PDF file of this meeting at the current state. + title: Xuất PDF + description: Tạo tệp PDF có thể in được của cuộc họp này ở trạng thái hiện tại. templates: default: - label: Mặc định - caption: The default template is suitable for most meetings and represent the current state. + label: mặc định + caption: Mẫu mặc định phù hợp với hầu hết các cuộc họp và thể hiện trạng thái hiện tại. minutes: - label: Biên bản cuộc họp - caption: The minutes template is suitable for closed and archived meetings. + label: phút + caption: Mẫu biên bản phù hợp cho các cuộc họp kín và lưu trữ. first_page_header_left: - label: First page header left - caption: This text will appear on the first page at the left of the header. + label: Tiêu đề trang đầu tiên bên trái + caption: Văn bản này sẽ xuất hiện trên trang đầu tiên ở bên trái của tiêu đề. author: - label: Tác giả - caption: The author of the minutes will be displayed in the subtitle. + label: tác giả + caption: Tác giả của biên bản sẽ được hiển thị trong phụ đề. include_participants: - label: Include list of participants - caption: A list of participants will be preset above the meeting agenda. + label: Bao gồm danh sách những người tham gia + caption: Một danh sách những người tham gia sẽ được cài sẵn phía trên chương trình họp. include_attachments: - label: Include list of attachments - caption: A list containing the filenames of attachments will be appended at the end. + label: Bao gồm danh sách các tệp đính kèm + caption: Một danh sách chứa tên tệp đính kèm sẽ được thêm vào cuối. include_backlog: - label: Include backlog - caption: Backlog elements are not normally considered part of a meeting occurrence but you can choose to include them. + label: Bao gồm tồn đọng + caption: Các thành phần tồn đọng thường không được coi là một phần của cuộc họp nhưng bạn có thể chọn đưa chúng vào. include_outcomes: - label: Include agenda outcomes - caption: If your agenda outcomes contain confidential information, you can choose to not include them in the export. + label: Bao gồm các kết quả chương trình nghị sự + caption: Nếu kết quả chương trình nghị sự của bạn chứa thông tin bí mật, bạn có thể chọn không đưa chúng vào khi xuất. footer_text: - label: Footer text - caption: This text will appear on every page at the center of the footer. - submit_button: Tải + label: Văn bản chân trang + caption: Văn bản này sẽ xuất hiện trên mỗi trang ở giữa chân trang. + submit_button: tải về notifications: sidepanel: - title: "Email calendar updates" + title: "Cập nhật lịch qua email" description: - disabled: "Participants will not receive an email informing them of changes." - enabled: "All participants will receive updated calendar invites via email informing them of changes." - change_via_template: "To change this, edit the series template." + disabled: "Những người tham gia sẽ không nhận được email thông báo về những thay đổi." + enabled: "Tất cả những người tham gia sẽ nhận được lời mời lịch cập nhật qua email thông báo cho họ về những thay đổi." + change_via_template: "Để thay đổi điều này, hãy chỉnh sửa mẫu chuỗi." dialog: title: - enable: "Enable email calendar updates?" - disable: "Disable email calendar updates?" + enable: "Bật cập nhật lịch email?" + disable: "Tắt cập nhật lịch email?" message: enable: > - All participants will receive updated calendar invites via email every time there is a change to the meeting date, time, location or participants. Once enabled, an email will be sent out immediately to all participants. + Tất cả những người tham gia sẽ nhận được lời mời lịch cập nhật qua email mỗi khi có thay đổi về ngày, giờ, địa điểm hoặc người tham gia cuộc họp. Sau khi được bật, một email sẽ được gửi ngay lập tức đến tất cả người tham gia. disable: > - Participants will no longer receive updated calendar invites via email when there are changes to the meeting date, time, location or participants. If they already had an invite for this meeting, it might no longer be accurate. + Người tham gia sẽ không còn nhận được lời mời lịch cập nhật qua email khi có thay đổi về ngày, giờ, địa điểm hoặc người tham gia cuộc họp. Nếu họ đã có lời mời tham dự cuộc họp này thì lời mời đó có thể không còn chính xác nữa. confirm_label: - enable: "Enable email updates" - disable: "Disable email updates" + enable: "Kích hoạt cập nhật email" + disable: "Vô hiệu hóa cập nhật email" banner: participants: enabled: > - All participants will receive updated calendar invites via email when you add or remove participants. + Tất cả người tham gia sẽ nhận được lời mời lịch cập nhật qua email khi bạn thêm hoặc xóa người tham gia. disabled: > - Email calendar updates are disabled. Participants will not receive an email informing them when you add or remove participants. + Cập nhật lịch email bị vô hiệu hóa. Người tham gia sẽ không nhận được email thông báo cho họ khi bạn thêm hoặc xóa người tham gia. onetime: enabled: > - All participants will receive updated calendar invites via email when you add or remove participants. + Tất cả người tham gia sẽ nhận được lời mời lịch cập nhật qua email khi bạn thêm hoặc xóa người tham gia. disabled: > - Participants will not receive an email informing them of changes to meeting date, time or participants. + Những người tham gia sẽ không nhận được email thông báo về những thay đổi về ngày, giờ họp hoặc người tham gia. occurrence: enabled: > - Email calendar updates are enabled for the meeting series. All participants will receive updated calendar invites informing them of your changes to this occurrence. + Cập nhật lịch email được bật cho chuỗi cuộc họp. Tất cả những người tham gia sẽ nhận được lời mời cập nhật trên lịch thông báo cho họ về những thay đổi của bạn đối với lần xuất hiện này. disabled: > - Email calendar updates are disabled for the meeting series. Participants will not receive an email informing them of your changes to this occurrence. + Cập nhật lịch email bị vô hiệu hóa cho chuỗi cuộc họp. Những người tham gia sẽ không nhận được email thông báo cho họ về những thay đổi của bạn đối với lần xuất hiện này. template: enabled: > - Email calendar updates are enabled for the meeting series. All participants will receive updated calendar invites informing them of your changes to this template or to individual occurrences. + Cập nhật lịch email được bật cho chuỗi cuộc họp. Tất cả những người tham gia sẽ nhận được lời mời lịch cập nhật thông báo cho họ về những thay đổi của bạn đối với mẫu này hoặc đối với các lần xuất hiện riêng lẻ. disabled: > - Email calendar updates are disabled for the meeting series. Participants will not receive an email informing them of your changes to this template or to individual occurrences. + Cập nhật lịch email bị vô hiệu hóa cho chuỗi cuộc họp. Những người tham gia sẽ không nhận được email thông báo cho họ về những thay đổi của bạn đối với mẫu này hoặc đối với các lần xuất hiện riêng lẻ. presentation_mode: - title: "Presentation Mode" - button_present: "Present" - exit: "Exit presentation" - current_item: "Current item" - total_items: "%{current} of %{total}" - previous: "Previous" - next: "Next" - no_items: "No agenda items" - no_items_flash: "There are no agenda items to present." + title: "Chế độ trình bày" + button_present: "hiện tại" + exit: "Thoát khỏi bài thuyết trình" + current_item: "Mục hiện tại" + total_items: "%{current} trong số %{total}" + previous: "trước đó" + next: "Tiếp theo" + no_items: "Không có mục chương trình nghị sự" + no_items_flash: "Không có mục chương trình nghị sự để trình bày." ical_response: - update_failed: "Could not update participation status." - meeting_not_found: "Meeting not found for the given UID." + update_failed: "Không thể cập nhật trạng thái tham gia." + meeting_not_found: "Không tìm thấy cuộc họp cho UID đã cho." meeting_section: - untitled_title: "Phần không tiêu đề" - delete_confirmation: "Xóa phần này cũng sẽ xóa tất cả các mục chương trình nghị sự của nó. Bạn có chắc chắn muốn làm điều này không?" + untitled_title: "Phần không có tiêu đề" + delete_confirmation: "Xóa phần này cũng sẽ xóa tất cả các mục trong chương trình làm việc của phần đó. Bạn có chắc chắn muốn làm điều này?" placeholder_title: "Phần mới" - empty_text: "Kéo các mục vào đây hoặc tạo một cái mới" + empty_text: "Kéo các mục vào đây hoặc tạo một mục mới" meeting_participant: participation_status: - needs_action: "No response" - accepted: "Accepted" - declined: "Declined" - tentative: "Maybe" - unknown: "Unknown" + needs_action: "Không có phản hồi" + accepted: "được chấp nhận" + declined: "từ chối" + tentative: "Có lẽ" + unknown: "không rõ" recurring_meeting: time_zone_difference_banner: - title: "Time zone difference" + title: "Chênh lệch múi giờ" description: > - The dates below are referencing the time zone of the meeting series (%{actual_zone}), not your local time zone (%{user_zone}). + Những ngày bên dưới đang tham chiếu múi giờ của chuỗi cuộc họp (%{actual_zone}), không phải múi giờ địa phương của bạn (%{user_zone}). ended_blankslate: - title: "Meeting series ended" - message: "This meeting series has come to an end. There are no upcoming meetings. " - action: "You can still view past occurrences or edit the meeting series to extend it." + title: "Chuỗi cuộc họp đã kết thúc" + message: "Chuỗi cuộc họp này đã kết thúc. Không có cuộc họp sắp tới." + action: "Bạn vẫn có thể xem các lần diễn ra trước đây hoặc chỉnh sửa chuỗi cuộc họp để mở rộng." occurrence: - infoline: "Cuộc họp này là một phần của chuỗi họp định kỳ." - error_no_next: "Không có lần tới cho cuộc họp này." - first_already_exists: "Lần đầu tiên của chuỗi họp này đã được khởi tạo." + infoline: "Cuộc họp này là một phần của chuỗi cuộc họp định kỳ." + error_no_next: "Không có sự xuất hiện tiếp theo cho cuộc họp này." + first_already_exists: "Lần xuất hiện đầu tiên của chuỗi cuộc họp này đã được khởi tạo." first_created: > - Cuộc họp đầu tiên đã được tạo thành công từ mẫu. Tất cả các cuộc họp trong tương lai sẽ được tạo tự động tại thời điểm xảy ra trước đó. + Cuộc họp đầu tiên đã được tạo thành công từ mẫu. Tất cả các cuộc họp trong tương lai sẽ được tạo tự động tại thời điểm diễn ra trước đó. template: - button_finalize: "Open first meeting" - blank_title: "Mẫu chuỗi họp của bạn đang trống" + button_finalize: "Mở cuộc họp đầu tiên" + blank_title: "Mẫu chuỗi cuộc họp của bạn trống" description: > - Mẫu này sẽ được sử dụng bất cứ khi nào các cuộc họp mới trong chuỗi được tạo. Bạn có thể thêm các mục chương trình làm việc, người tham gia và tệp đính kèm vào mẫu này. - label_view_template: "Xem Mẫu" - label_edit_template: "Sửa mẫu" + Mẫu này sẽ được sử dụng bất cứ khi nào các cuộc họp mới trong chuỗi được tạo. Bạn có thể thêm các mục chương trình nghị sự, người tham gia và tệp đính kèm vào mẫu này. + label_view_template: "Xem mẫu" + label_edit_template: "Chỉnh sửa mẫu" banner_html: > - You are currently editing a template of a meeting series: %{link}. Every new occurrence of a meeting in the series will use this template. Changes will not affect past or already-created meetings. + Bạn hiện đang chỉnh sửa mẫu chuỗi cuộc họp: %{link}. Mọi cuộc họp mới diễn ra trong chuỗi cuộc họp sẽ sử dụng mẫu này. Những thay đổi sẽ không ảnh hưởng đến các cuộc họp trong quá khứ hoặc đã được tạo. draft_banner_html: > - You are currently editing a template of a meeting series: %{link}. Every new occurrence of a meeting in the series will use this template. Changes will not affect past or already-created meetings. No email invites will be sent in this current draft mode until you open the first meeting. + Bạn hiện đang chỉnh sửa mẫu chuỗi cuộc họp: %{link}. Mọi cuộc họp mới diễn ra trong chuỗi cuộc họp sẽ sử dụng mẫu này. Những thay đổi sẽ không ảnh hưởng đến các cuộc họp trong quá khứ hoặc đã được tạo. Sẽ không có lời mời qua email nào được gửi ở chế độ nháp hiện tại này cho đến khi bạn mở cuộc họp đầu tiên. frequency: x_daily: - other: "Every %{count} days" + other: "Cứ %{count} ngày" x_weekly: - other: "Every %{count} weeks" - every_weekday: "Mỗi %{day_of_the_week}" + other: "Mỗi %{count} tuần" + every_weekday: "Mọi %{day_of_the_week}" working_days: "Mỗi ngày làm việc" end_after: never: "không bao giờ" - specific_date: "sau ngày cụ thể" - iterations: "sau số lần họp" - starts: "Bắt đầu" + specific_date: "sau một ngày cụ thể" + iterations: "sau một số lần xuất hiện" + starts: "bắt đầu" in_words: - daily_interval: "Mỗi %{interval} ngày" + daily_interval: "Cứ %{interval} ngày" working_days: "Mỗi ngày làm việc" - weekly: "Mỗi tuần vào %{weekday}" - weekly_interval: "Mỗi %{interval} tuần vào %{weekday}" - frequency: "%{base} vào %{time}" - full: "%{base} vào %{time}, kết thúc vào %{end_date}" - full_past: "%{base} at %{time}, ended on %{end_date}" - never_ending: "%{base} vào %{time}" + weekly: "Hàng tuần vào %{weekday}" + weekly_interval: "Cứ %{interval} tuần vào %{weekday}" + frequency: "%{base} tại %{time}" + full: "%{base} lúc %{time}, kết thúc vào %{end_date}" + full_past: "%{base} lúc %{time}, đã kết thúc vào %{end_date}" + never_ending: "%{base} tại %{time}" open: - title: "Agenda opened" + title: "Đã mở chương trình làm việc" subtitle: > - Open meetings have agendas that can be edited and show up in individual users’ ‘My meetings’ section. Changes to the meeting series template do not affect already-open meeting occurrences. + Các cuộc họp mở có các chương trình nghị sự có thể được chỉnh sửa và hiển thị trong phần 'Cuộc họp của tôi' của người dùng cá nhân. Những thay đổi đối với mẫu chuỗi cuộc họp không ảnh hưởng đến các lần diễn ra cuộc họp đã mở. blankslate: - title: "No open meetings at the moment" - desc: "You can manually open a planned meeting by clicking on the 'Open' button below" + title: "Hiện tại không có cuộc họp mở nào" + desc: "Bạn có thể mở cuộc họp đã lên kế hoạch theo cách thủ công bằng cách nhấp vào nút 'Mở' bên dưới" planned: - title: "Dự kiến" + title: "Đã lên kế hoạch" subtitle: > - The following meetings are planned in the recurring meeting schedule but are not open yet. Every time a planned meeting starts, the next one will automatically be opened for you. You can also open planned meetings manually to import the template and start editing the agenda. + Các cuộc họp sau đây được lên kế hoạch trong lịch họp định kỳ nhưng vẫn chưa mở. Mỗi khi cuộc họp theo kế hoạch bắt đầu, cuộc họp tiếp theo sẽ tự động được mở cho bạn. Bạn cũng có thể mở các cuộc họp đã lên kế hoạch theo cách thủ công để nhập mẫu và bắt đầu chỉnh sửa chương trình nghị sự. blankslate: - title: "No more planned meetings" + title: "Không còn cuộc họp theo kế hoạch" desc: > - There are no additional meetings planned in this series. To schedule additional meetings or extend the series, go to the template and edit meeting details to change the end date, frequency or interval. + Không có cuộc họp bổ sung nào được lên kế hoạch trong loạt bài này. Để lên lịch các cuộc họp bổ sung hoặc mở rộng chuỗi cuộc họp, hãy chuyển đến mẫu và chỉnh sửa chi tiết cuộc họp để thay đổi ngày, tần suất hoặc khoảng thời gian kết thúc. delete_dialog: - title: "Xóa chuỗi các cuộc họp" - heading: "Permanently delete this meeting series?" + title: "Xóa chuỗi cuộc họp" + heading: "Xóa vĩnh viễn chuỗi cuộc họp này?" confirmation_message_html: zero: > - The meeting series %{title} does not have any meeting occurrences. The series will be deleted for everyone. Please proceed with caution. + Chuỗi cuộc họp %{title} không có bất kỳ cuộc họp nào diễn ra. Bộ truyện sẽ bị xóa đối với tất cả mọi người. Hãy tiến hành thận trọng. one: > - Deleting %{title} will also delete one occurrence in this series. This action is not reversible. Please proceed with caution. + Xóa %{title} cũng sẽ xóa một lần xuất hiện trong chuỗi này. Hành động này không thể đảo ngược. Hãy tiến hành thận trọng. other: > - Deleting %{title} will delete all %{count} occurrences in this series. This action is not reversible. Please proceed with caution. + Xóa %{title} sẽ xóa tất cả %{count} lần xuất hiện trong chuỗi này. Hành động này không thể đảo ngược. Hãy tiến hành thận trọng. scheduled_delete_dialog: - title: "Cancel meeting occurrence" - heading: "Cancel this meeting occurrence?" + title: "Hủy cuộc họp diễn ra" + heading: "Hủy cuộc họp này diễn ra?" confirmation_message_html: > - Any meeting information not in the template will be lost. Do you want to continue? - confirm_button: "Cancel occurrence" + Mọi thông tin cuộc họp không có trong mẫu sẽ bị mất. Bạn có muốn tiếp tục không? + confirm_button: "Hủy lần xuất hiện" end_series_dialog: - title: "End meeting series" - notice_successful_notification: "Email calendar update sent to all participants" + title: "Kết thúc chuỗi cuộc họp" + notice_successful_notification: "Cập nhật lịch qua email được gửi tới tất cả người tham gia" notice_timezone_missing: Chưa đặt múi giờ và %{zone} đã được chọn. Để chọn múi giowf của bạn, nhấn vào đây. - notice_meeting_updated: "Trang này đã được người khác cập nhật. Tải lại để xem thay đổi." + notice_meeting_updated: "Trang này đã được cập nhật bởi người khác. Tải lại để xem các thay đổi." permission_create_meetings: "Tạo cuộc họp" permission_edit_meetings: "Chỉnh sửa cuộc họp" permission_delete_meetings: "Xóa cuộc họp" permission_view_meetings: "Xem cuộc họp" permission_manage_agendas: "Quản lý chương trình nghị sự" - permission_manage_agendas_explanation: "Allows creating, editing and removing agenda items" - permission_manage_outcomes: "Manage outcomes" - permission_send_meeting_invites_and_outcomes: "Send meeting invites and outcomes to participants" - project_module_meetings: "Các cuộc họp" + permission_manage_agendas_explanation: "Cho phép tạo, chỉnh sửa và xóa các mục chương trình nghị sự" + permission_manage_outcomes: "Quản lý kết quả" + permission_send_meeting_invites_and_outcomes: "Gửi lời mời họp và kết quả cho người tham gia" + project_module_meetings: "các cuộc họp" text_duration_in_hours: "Thời lượng tính bằng giờ" text_in_hours: "bằng giờ" text_meeting_agenda_for_meeting: 'Các ý chính cho cuộc họp "%{meeting}"' - text_meeting_series_end_early_heading: "Delete future occurrences?" - text_meeting_series_end_early: "Ending the series will delete any future open or scheduled meeting occurrences" - text_meeting_closing_are_you_sure: "Bạn có chắc chắn muốn đóng chương trình nghị sự cuộc họp không?" - text_meeting_agenda_open_are_you_sure: "Điều này sẽ ghi đè tất cả các thay đổi trong biên bản! Bạn có muốn tiếp tục không?" + text_meeting_series_end_early_heading: "Xóa các lần xuất hiện trong tương lai?" + text_meeting_series_end_early: "Việc kết thúc chuỗi sẽ xóa mọi cuộc họp mở hoặc đã lên lịch trong tương lai" + text_meeting_closing_are_you_sure: "Bạn có chắc chắn muốn đóng chương trình cuộc họp không?" + text_meeting_agenda_open_are_you_sure: "Điều này sẽ ghi đè lên tất cả các thay đổi trong vài phút! Bạn có muốn tiếp tục không?" text_meeting_minutes_for_meeting: 'biên bản cuộc họp "%{meeting}"' - text_notificiation_invited: "Email này chứa một mục ics cho cuộc họp dưới đây:" + text_notificiation_invited: "Thư này chứa mục nhập ics cho cuộc họp bên dưới:" text_meeting_ics_description: >- - Link to meeting: %{url} + Link cuộc họp: %{url} text_meeting_ics_meeting_series_description: >- - Link to meeting series: %{url} + Link chuỗi cuộc họp: %{url} text_meeting_occurrence_ics_description: >- - Link to meeting occurrence: %{url} Link to meeting series: %{series_url} + Link diễn ra cuộc họp: %{url} Link chuỗi cuộc họp: %{series_url} text_meeting_empty_heading: "Cuộc họp của bạn trống" - text_meeting_empty_description1: "Bắt đầu bằng cách thêm các mục chương trình nghị sự dưới đây. Mỗi mục có thể đơn giản chỉ là tiêu đề, nhưng bạn cũng có thể thêm các chi tiết bổ sung như thời gian và ghi chú." - text_meeting_empty_description2: 'Bạn cũng có thể thêm các tham chiếu đến các gói công việc hiện có. Khi bạn làm như vậy, các ghi chú liên quan sẽ tự động hiển thị trong tab "Cuộc họp" của gói công việc.' + text_meeting_empty_description1: "Bắt đầu bằng cách thêm các mục chương trình nghị sự bên dưới. Mỗi mục có thể đơn giản chỉ là tiêu đề nhưng bạn cũng có thể thêm các chi tiết bổ sung như thời lượng và ghi chú." + text_meeting_empty_description2: 'Bạn cũng có thể thêm tài liệu tham khảo vào các gói công việc hiện có. Khi bạn làm như vậy, các ghi chú liên quan sẽ tự động hiển thị trong tab "Cuộc họp" của gói công việc.' label_meeting_empty_action: "Thêm mục chương trình nghị sự" - label_meeting_actions: "Các hành động cuộc họp" + label_meeting_actions: "Hành động cuộc họp" label_meeting_edit_title: "Chỉnh sửa tiêu đề cuộc họp" label_meeting_delete: "Xóa cuộc họp" - label_meeting_created_by: "Được tạo bởi" + label_meeting_created_by: "Tạo bởi" label_meeting_last_updated: "Cập nhật lần cuối" - label_meeting_reload: "Tải lại" - label_meeting_index_today: "Hôm nay" - label_meeting_index_tomorrow: "Tomorrow" - label_meeting_index_this_week: "Later this week" - label_meeting_index_later: "Next week and later" - label_agenda_items: "Các mục chương trình nghị sự" - label_agenda_items_reordered: "đã sắp xếp lại" + label_meeting_reload: "tải lại" + label_meeting_index_today: "hôm nay" + label_meeting_index_tomorrow: "Ngày mai" + label_meeting_index_this_week: "Cuối tuần này" + label_meeting_index_later: "Tuần sau và sau đó" + label_agenda_items: "Mục chương trình nghị sự" + label_agenda_items_reordered: "sắp xếp lại" label_agenda_item_add: "Thêm mục chương trình nghị sự" - label_agenda_item_remove_from_agenda: "Gỡ bỏ khỏi chương trình nghị sự" - label_agenda_item_remove_from_backlog: "Remove from backlog" + label_agenda_item_remove_from_agenda: "Xóa khỏi chương trình nghị sự" + label_agenda_item_remove_from_backlog: "Xóa khỏi hồ sơ tồn đọng" label_agenda_item_undisclosed_wp: "Gói công việc #%{id} không hiển thị" - label_agenda_item_deleted_wp: "Tham chiếu gói công việc đã bị xóa" - label_agenda_item_actions: "Các hành động mục chương trình nghị sự" - label_agenda_item_move_to_next_title: "Move to next meeting?" + label_agenda_item_deleted_wp: "Đã xóa tham chiếu gói công việc" + label_agenda_item_actions: "Hành động của mục chương trình nghị sự" + label_agenda_item_move_to_next_title: "Chuyển sang cuộc họp tiếp theo?" label_agenda_item_move: "Di chuyển" - label_agenda_item_move_to_next: "Move to next meeting" - label_agenda_item_move_to_backlog: "Move to backlog" - label_agenda_item_move_to_current_meeting: "Move to current meeting" - label_agenda_item_move_to_section: "Move to section" - label_agenda_item_move_to_top: "Lên trên cùng" - label_agenda_item_move_to_bottom: "Chuyển đến dưới cùng" + label_agenda_item_move_to_next: "Chuyển sang cuộc họp tiếp theo" + label_agenda_item_move_to_backlog: "Chuyển sang tồn đọng" + label_agenda_item_move_to_current_meeting: "Di chuyển đến cuộc họp hiện tại" + label_agenda_item_move_to_section: "Di chuyển đến phần" + label_agenda_item_move_to_top: "Di chuyển lên trên cùng" + label_agenda_item_move_to_bottom: "Di chuyển xuống dưới cùng" label_agenda_item_move_up: "Di chuyển lên" label_agenda_item_move_down: "Dịch xuống" - label_agenda_item_duplicate: "Duplicate" - label_agenda_item_duplicate_in_next: "Duplicate in next occurrence" - label_agenda_item_duplicate_in_next_title: "Duplicate in next occurrence?" + label_agenda_item_duplicate: "trùng lặp" + label_agenda_item_duplicate_in_next: "Trùng lặp trong lần xuất hiện tiếp theo" + label_agenda_item_duplicate_in_next_title: "Trùng lặp trong lần xuất hiện tiếp theo?" label_agenda_item_add_notes: "Thêm ghi chú" - label_agenda_item_add_outcome: "Add outcome" - label_agenda_item_work_package_add: "Add work package" + label_agenda_item_add_outcome: "Thêm kết quả" + label_agenda_item_work_package_add: "Thêm gói công việc" label_agenda_item_work_package: "Gói công việc mục chương trình nghị sự" - label_section_rename: "Rename section" - label_agenda_outcome: "Outcome" - label_agenda_new_outcome: "New outcome" - label_agenda_outcome_actions: "Agenda outcome actions" - label_agenda_outcome_edit: "Edit outcome" - label_agenda_outcome_delete: "Remove outcome" - label_added_as_outcome: "Added as outcome" - label_write_outcome: "Write outcome" - label_existing_work_package: "Existing work package" - text_outcome_not_editable_anymore: "This outcome is not editable anymore." - text_outcome_cannot_be_added: "An outcome can no longer be added." - label_backlog_clear: "Clear backlog" + label_section_rename: "Đổi tên phần" + label_agenda_outcome: "kết quả" + label_agenda_new_outcome: "Kết quả mới" + label_agenda_outcome_actions: "Hành động kết quả chương trình nghị sự" + label_agenda_outcome_edit: "Chỉnh sửa kết quả" + label_agenda_outcome_delete: "Xóa kết quả" + label_added_as_outcome: "Đã thêm dưới dạng kết quả" + label_write_outcome: "Viết kết quả" + label_existing_work_package: "Gói công việc hiện có" + text_outcome_not_editable_anymore: "Kết quả này không thể chỉnh sửa được nữa." + text_outcome_cannot_be_added: "Một kết quả không còn có thể được thêm vào." + label_backlog_clear: "Xóa tồn đọng" label_backlog_clear_button: "Xóa tất cả" - text_backlog_clear_error: "One or more errors occurred while trying to clear the backlog." - label_agenda_backlog: "Agenda backlog" + text_backlog_clear_error: "Đã xảy ra một hoặc nhiều lỗi khi cố gắng xóa hồ sơ tồn đọng." + label_agenda_backlog: "tồn đọng chương trình làm việc" text_agenda_backlog: > - This backlog is unique to this one-time meeting. You can drag items in and out to add or remove them from the meeting agenda. - label_agenda_backlog_clear_title: "Clear agenda backlog?" + Việc tồn đọng này là duy nhất cho cuộc họp một lần này. Bạn có thể kéo các mục vào và ra để thêm hoặc xóa chúng khỏi chương trình cuộc họp. + label_agenda_backlog_clear_title: "Xóa lịch trình tồn đọng?" text_agenda_backlog_clear_description: > - Are you sure you want to remove all items currently in the agenda backlog? This action is not reversible. - label_series_backlog: "Series backlog" + Bạn có chắc chắn muốn xóa tất cả các mục hiện có trong lịch trình tồn đọng không? Hành động này không thể đảo ngược. + label_series_backlog: "tồn đọng hàng loạt" text_series_backlog: > - The backlog is shared with all occurrences of this series. You can drag items in and out to add or remove them from a particular meeting. - label_series_backlog_clear_title: "Clear series backlog?" + Việc tồn đọng được chia sẻ với tất cả các lần xuất hiện của loạt bài này. Bạn có thể kéo các mục vào và ra để thêm hoặc xóa chúng khỏi một cuộc họp cụ thể. + label_series_backlog_clear_title: "Xóa số tồn đọng của loạt phim?" text_series_backlog_clear_description: > - This will remove all items in the series backlog, which is shared with all meetings in the series. Are you sure you want to continue? This action is not reversible. + Thao tác này sẽ xóa tất cả các mục trong chuỗi tồn đọng, được chia sẻ với tất cả các cuộc họp trong chuỗi. Bạn có chắc chắn muốn tiếp tục không? Hành động này không thể đảo ngược. text_agenda_item_title: 'Mục chương trình nghị sự "%{title}"' - text_agenda_work_package_deleted: "Mục chương trình nghị sự cho gói công việc đã bị xóa" - text_deleted_agenda_item: "Mục chương trình nghị sự đã bị xóa" + text_agenda_work_package_deleted: "Mục chương trình nghị sự cho gói công việc đã xóa" + text_deleted_agenda_item: "Đã xóa mục chương trình nghị sự" label_initial_meeting_details: "Cuộc họp" label_meeting_details: "Chi tiết cuộc họp" - label_meeting_series_details: "Chi tiết chuỗi họp" + label_meeting_series_details: "Chi tiết chuỗi cuộc họp" label_meeting_details_edit: "Chỉnh sửa chi tiết cuộc họp" label_meeting_state: "Trạng thái cuộc họp" - label_meeting_state_draft: "Draft" - label_meeting_state_open: "Mở" - label_meeting_state_closed: "Đã đóng" - label_meeting_state_agenda_created: "Agenda đã tạo" - label_meeting_state_planned: "Dự kiến" + label_meeting_state_draft: "dự thảo" + label_meeting_state_open: "mở" + label_meeting_state_closed: "đóng cửa" + label_meeting_state_agenda_created: "Đã tạo chương trình làm việc" + label_meeting_state_planned: "Đã lên kế hoạch" label_meeting_state_cancelled: "Đã hủy" - label_meeting_state_skipped: "Đã bỏ qua" - label_meeting_state_in_progress: "Đang xử lý" + label_meeting_state_skipped: "bỏ qua" + label_meeting_state_in_progress: "Đang tiến hành" label_meeting_reopen_action: "Mở lại cuộc họp" - label_meeting_close_action: "Đóng cuộc họp" - label_meeting_in_progress_action: "Start meeting" - label_meeting_open_action: "Open meeting" - text_meeting_draft_description: "Prepare your agenda in draft mode. This meeting will not send out any calendar updates or invites, even if you change meeting details or add/remove participants." - text_meeting_open_description: "You can add/remove agenda items and participants. Once the agenda is ready, mark it as in progress to document outcomes." - text_meeting_closed_description: "Cuộc họp này đã đóng. Bạn không thể thêm/xóa các mục chương trình nghị sự nữa." - text_meeting_in_progress_description: "You can modify the agenda, document outcomes for each item and track attendance for participants. Once the meeting is complete, you can mark it as closed to lock it." - text_meeting_open_dropdown_description: "Any existing outcomes will remain but users will not be able to add new outcomes." - text_meeting_in_progress_dropdown_description: "Document outcomes like information needs or decisions taken during the meeting." - text_meeting_closed_dropdown_description: "This meeting is closed. You cannot modify agenda items or outcomes anymore." - text_meeting_draft_banner: "You are currently in draft mode. This meeting will not send out any calendar updates or invites, even if you change meeting details or add/remove participants." - text_exit_draft_mode_dialog_title: "Open this meeting and send invites?" - text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." - text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" - text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." - text_meeting_not_editable_anymore: "Cuộc họp này không còn có thể chỉnh sửa nữa." - text_meeting_not_present_anymore: "Cuộc họp này đã bị xóa. Vui lòng chọn cuộc họp khác." - label_add_work_package_to_meeting_dialog_title: "Select meeting" - label_add_work_package_to_meeting_section_label: "Phần" + label_meeting_close_action: "Kết thúc cuộc họp" + label_meeting_in_progress_action: "Bắt đầu cuộc họp" + label_meeting_open_action: "Cuộc họp mở" + text_meeting_draft_description: "Chuẩn bị chương trình nghị sự của bạn ở chế độ dự thảo. Cuộc họp này sẽ không gửi bất kỳ cập nhật lịch hoặc lời mời nào, ngay cả khi bạn thay đổi chi tiết cuộc họp hoặc thêm/xóa người tham gia." + text_meeting_open_description: "Bạn có thể thêm/xóa các mục chương trình nghị sự và người tham gia. Khi chương trình nghị sự đã sẵn sàng, hãy đánh dấu nó là đang tiến hành để ghi lại kết quả." + text_meeting_closed_description: "Cuộc họp này đã kết thúc. Bạn không thể thêm/xóa các mục trong chương trình nghị sự nữa." + text_meeting_in_progress_description: "Bạn có thể sửa đổi chương trình nghị sự, ghi lại kết quả cho từng mục và theo dõi sự tham dự của người tham gia. Sau khi cuộc họp kết thúc, bạn có thể đánh dấu cuộc họp là đã đóng để khóa cuộc họp." + text_meeting_open_dropdown_description: "Mọi kết quả hiện có sẽ vẫn giữ nguyên nhưng người dùng sẽ không thể thêm kết quả mới." + text_meeting_in_progress_dropdown_description: "Ghi lại các kết quả như nhu cầu thông tin hoặc các quyết định được đưa ra trong cuộc họp." + text_meeting_closed_dropdown_description: "Cuộc họp này đã kết thúc. Bạn không thể sửa đổi các mục hoặc kết quả của chương trình nghị sự nữa." + text_meeting_draft_banner: "Bạn hiện đang ở chế độ nháp. Cuộc họp này sẽ không gửi bất kỳ cập nhật lịch hoặc lời mời nào, ngay cả khi bạn thay đổi chi tiết cuộc họp hoặc thêm/xóa người tham gia." + text_exit_draft_mode_dialog_title: "Mở cuộc họp này và gửi lời mời?" + text_exit_draft_mode_dialog_subtitle: "Bạn không thể quay lại chế độ nháp sau khi lên lịch cuộc họp." + text_exit_draft_mode_dialog_template_title: "Mở lần xuất hiện đầu tiên của chuỗi cuộc họp này?" + text_exit_draft_mode_dialog_template_subtitle: "Bạn không thể quay lại chế độ nháp sau này." + text_meeting_not_editable_anymore: "Cuộc họp này không thể chỉnh sửa được nữa." + text_meeting_not_present_anymore: "Cuộc họp này đã bị xóa. Vui lòng chọn một cuộc họp khác." + label_add_work_package_to_meeting_dialog_title: "Chọn cuộc họp" + label_add_work_package_to_meeting_section_label: "phần" label_add_work_package_to_meeting_dialog_button: "Thêm vào cuộc họp" - label_meeting_selection_caption: "It is only possible to add this work package to an upcoming or an ongoing meeting." - label_section_selection_caption: "Choose a particular section of the agenda or add it to the backlog." - placeholder_section_select_meeting_first: "Meeting selection is required first" - text_add_work_package_to_meeting_form: "The work package will be added to the selected meeting or backlog as an agenda item." - text_add_work_package_to_meeting_description: "Một gói công việc có thể được thêm vào một hoặc nhiều cuộc họp để thảo luận. Bất kỳ ghi chú nào liên quan cũng sẽ hiển thị ở đây." - text_agenda_item_no_notes: "Không có ghi chú được cung cấp" - text_agenda_item_not_editable_anymore: "Mục chương trình nghị sự này không còn có thể chỉnh sửa nữa." - text_agenda_item_move_next_meeting: "This item will be moved to the next meeting on %{date} at %{time}." - text_agenda_item_moved_to_next_meeting: "Agenda item moved to the next meeting, on %{date}" - text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." - text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" - text_work_package_has_no_upcoming_meeting_agenda_items: "Gói công việc này chưa được lên lịch trong chương trình nghị sự cuộc họp sắp tới." - text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." - text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." - text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." - text_work_package_add_to_meeting_hint: 'Sử dụng nút "Thêm vào cuộc họp" để thêm gói công việc này vào một cuộc họp sắp tới.' - text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." - text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." - text_email_updates_enabled: "Email calendar updates are enabled. All participants will receive updated invites via email when you make changes." + label_meeting_selection_caption: "Chỉ có thể thêm gói công việc này vào cuộc họp sắp tới hoặc đang diễn ra." + label_section_selection_caption: "Chọn một phần cụ thể của chương trình nghị sự hoặc thêm nó vào hồ sơ tồn đọng." + placeholder_section_select_meeting_first: "Lựa chọn cuộc họp là bắt buộc trước tiên" + text_add_work_package_to_meeting_form: "Gói công việc sẽ được thêm vào cuộc họp đã chọn hoặc hồ sơ tồn đọng dưới dạng mục chương trình nghị sự." + text_add_work_package_to_meeting_description: "Một gói công việc có thể được thêm vào một hoặc nhiều cuộc họp để thảo luận. Bất kỳ ghi chú nào liên quan đến nó cũng có thể nhìn thấy ở đây." + text_agenda_item_no_notes: "Không có ghi chú nào được cung cấp" + text_agenda_item_not_editable_anymore: "Mục chương trình nghị sự này không thể chỉnh sửa được nữa." + text_agenda_item_move_next_meeting: "Mục này sẽ được chuyển sang cuộc họp tiếp theo vào %{date} lúc %{time}." + text_agenda_item_moved_to_next_meeting: "Mục chương trình nghị sự đã được chuyển sang cuộc họp tiếp theo, vào %{date}" + text_agenda_item_duplicate_in_next_meeting: "Bạn có chắc chắn muốn thêm bản sao của mục chương trình này vào cuộc họp tiếp theo, vào %{date} lúc %{time} không? Kết quả sẽ không bị trùng lặp." + text_agenda_item_duplicated_in_next_meeting: "Mục chương trình nghị sự được sao chép trong cuộc họp tiếp theo, vào ngày %{date}" + text_work_package_has_no_upcoming_meeting_agenda_items: "Gói công việc này chưa được lên lịch trong chương trình họp sắp tới." + text_agenda_item_no_available_occurrence: "Tất cả các sự kiện sắp tới đã bị hủy bỏ." + text_agenda_item_dialog_skipping_cancelled_one: "Lưu ý: Bỏ qua sự kiện bị hủy trên %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Lưu ý: Bỏ qua các trường hợp bị hủy trong \" %{count}\"." + text_work_package_add_to_meeting_hint: 'Sử dụng nút "Thêm vào cuộc họp" để thêm gói công việc này vào cuộc họp sắp tới.' + text_work_package_has_no_past_meeting_agenda_items: "Gói công việc này chưa được thêm vào làm mục chương trình nghị sự trong cuộc họp trước đây." + text_email_updates_muted: "Cập nhật lịch email bị tắt tiếng. Người tham gia sẽ không nhận được lời mời cập nhật qua email khi bạn thực hiện thay đổi." + text_email_updates_enabled: "Cập nhật lịch email được kích hoạt. Tất cả người tham gia sẽ nhận được lời mời cập nhật qua email khi bạn thực hiện thay đổi." my_account: access_tokens: token/ical_meeting: - blank_description: "You can create one using the button below." - blank_title: "No iCalendar meeting token" - title: "iCalendar for meetings" - table_title: "iCalendar meeting tokens" - text_hint: "iCalendar meeting tokens allow users to subscribe to all their meetings and view up-to-date meeting information in external clients." - disabled_text: "iCalendar meeting subscriptions are not enabled by the administrator. Please contact your administrator to use this feature." + blank_description: "Bạn có thể tạo một cái bằng cách sử dụng nút bên dưới." + blank_title: "Không có mã thông báo cuộc họp iCalendar" + title: "iCalendar cho cuộc họp" + table_title: "Mã thông báo cuộc họp iCalendar" + text_hint: "Mã thông báo cuộc họp iCalendar cho phép người dùng đăng ký tất cả các cuộc họp của họ và xem thông tin cuộc họp cập nhật trong các ứng dụng khách bên ngoài." + disabled_text: "Quản trị viên không kích hoạt đăng ký cuộc họp iCalendar. Vui lòng liên hệ với quản trị viên của bạn để sử dụng tính năng này." add_button: "Đăng ký lịch" my: access_token: dialog: token/ical_meeting: - dialog_title: "New iCal subscription token for meetings" - dialog_body: "This token will generate an iCal subscription URL that lets you view all your meetings in an external calendar application." - create_button: "Create subscription" - name_label: "Token name" - name_caption: 'You can name it after where you will use it, such as "My phone" or "Work computer".' + dialog_title: "Mã thông báo đăng ký iCal mới cho cuộc họp" + dialog_body: "Mã thông báo này sẽ tạo URL đăng ký iCal cho phép bạn xem tất cả các cuộc họp của mình trong ứng dụng lịch bên ngoài." + create_button: "Tạo đăng ký" + name_label: "Tên mã thông báo" + name_caption: 'Bạn có thể đặt tên cho nó theo nơi bạn sẽ sử dụng, chẳng hạn như "Điện thoại của tôi" hoặc "Máy tính ở cơ quan".' created_dialog: token/ical_meeting: - title: "An iCal meeting subscription token has been generated" - body: "Treat the following URL as you would a password. Anyone who has access to it can view all your meetings." + title: "Mã thông báo đăng ký cuộc họp iCal đã được tạo" + body: "Hãy coi URL sau đây như một mật khẩu. Bất cứ ai có quyền truy cập vào nó đều có thể xem tất cả các cuộc họp của bạn." revocation: token/ical_meeting: - notice_success: "The iCalendar meeting subscription has been revoked successfully." - notice_failure: "Failed to revoke iCalendar meeting subscription: %{error}" + notice_success: "Đăng ký cuộc họp iCalendar đã được thu hồi thành công." + notice_failure: "Không thể thu hồi đăng ký cuộc họp iCalendar: %{error}" diff --git a/modules/meeting/config/locales/crowdin/zh-CN.yml b/modules/meeting/config/locales/crowdin/zh-CN.yml index d95e32a51e3..820092ff57f 100644 --- a/modules/meeting/config/locales/crowdin/zh-CN.yml +++ b/modules/meeting/config/locales/crowdin/zh-CN.yml @@ -231,15 +231,15 @@ zh-CN: summary: "%{actor} 已取消 '%{title}'。" date_time: "预定日期/时间" participant_added: - header: "Meeting '%{title}' - Participant added" - header_series: "Meeting series '%{title}' - Participant added" - summary: "%{actor} added %{participant} to the meeting '%{title}'" - summary_series: "%{actor} added %{participant} to the meeting series '%{title}'" + header: "会议 '%{title}' - 已添加参加者" + header_series: "会议系列 '%{title}' - 已添加参加者" + summary: "%{actor} 将 %{participant} 添加到会议 '%{title}'" + summary_series: "%{actor} 将 %{participant} 添加到会议系列 '%{title}'" participant_removed: - header: "Meeting '%{title}' - Participant removed" - header_series: "Meeting series '%{title}' - Participant removed" - summary: "%{actor} removed %{participant} from the meeting '%{title}'" - summary_series: "%{actor} removed %{participant} from the meeting series '%{title}'" + header: "会议 '%{title}' - 已移除参加者" + header_series: "会议系列 '%{title}' - 已移除参加者" + summary: "%{actor} 将 %{participant} 从会议 '%{title}' 中移除" + summary_series: "%{actor} 将 %{participant} 从会议系列 '%{title}' 中移除" ended: header_series: "结束:会议系列 '%{title}'" summary_series: "会议系列 '%{title}' 已由 %{actor} 结束。" @@ -535,9 +535,9 @@ zh-CN: label_agenda_outcome_actions: "议程成果行动" label_agenda_outcome_edit: "编辑成果" label_agenda_outcome_delete: "删除成果" - label_added_as_outcome: "Added as outcome" - label_write_outcome: "Write outcome" - label_existing_work_package: "Existing work package" + label_added_as_outcome: "已添加为成果" + label_write_outcome: "编写成果" + label_existing_work_package: "现有工作包" text_outcome_not_editable_anymore: "此成果已不可编辑。" text_outcome_cannot_be_added: "无法再添加结果。" label_backlog_clear: "清除待办事项" @@ -604,9 +604,9 @@ zh-CN: text_agenda_item_duplicate_in_next_meeting: "确定要将此议程条目的副本添加到 %{date} %{time} 的下一个会议吗?结果不会复制。" text_agenda_item_duplicated_in_next_meeting: "在 %{date}的下一个会议中复制的议程条目" text_work_package_has_no_upcoming_meeting_agenda_items: "该工作包尚未被安排到即将举行的会议议程中。" - text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." - text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." - text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." + text_agenda_item_no_available_occurrence: "所有即将发生的事件均已取消。" + text_agenda_item_dialog_skipping_cancelled_one: "注:跳过 %{date} 已取消的事件。" + text_agenda_item_dialog_skipping_cancelled_many: "注:跳过 %{count} 个已取消的事件。" text_work_package_add_to_meeting_hint: '使用"添加到会议"按钮将此工作包添加到即将举行的会议。' text_work_package_has_no_past_meeting_agenda_items: "此工作包未作为过往会议中的议程条目添加。" text_email_updates_muted: "电子邮件日历更新已静音。当您进行更改时,与会者将不会通过电子邮件收到更新的邀请。" @@ -614,10 +614,10 @@ zh-CN: my_account: access_tokens: token/ical_meeting: - blank_description: "You can create one using the button below." - blank_title: "No iCalendar meeting token" + blank_description: "您可以使用下方按钮创建一个。" + blank_title: "没有 iCalendar 会议令牌" title: "会议 iCalendar" - table_title: "iCalendar meeting tokens" + table_title: "iCalendar 会议令牌" text_hint: "iCalendar 会议令牌允许用户订阅其所有会议,并允许用户在外部客户端中查看最新会议信息。" disabled_text: "管理员未启用 iCalendar 会议订阅。请联系管理员以使用此功能。" add_button: "订阅日历" diff --git a/modules/openid_connect/config/locales/crowdin/vi.yml b/modules/openid_connect/config/locales/crowdin/vi.yml index 6882fbb1934..29b1b5db50a 100644 --- a/modules/openid_connect/config/locales/crowdin/vi.yml +++ b/modules/openid_connect/config/locales/crowdin/vi.yml @@ -1,155 +1,155 @@ vi: plugin_openproject_openid_connect: - name: "OpenProject OpenID Connect" - description: "Thêm các nhà cung cấp chiến lược OmniAuth OpenID Connect vào OpenProject." + name: "OpenProject OpenID Kết nối" + description: "Thêm nhà cung cấp chiến lược OmniAuth OpenID Connect vào OpenProject." logout_warning: > - Bạn đã được đăng xuất. Nội dung của bất kỳ biểu mẫu nào bạn gửi có thể bị mất. Vui lòng [đăng nhập]. + Bạn đã đăng xuất. Nội dung của bất kỳ hình thức nào bạn gửi có thể bị mất. Xin vui lòng [log in]. activerecord: attributes: openid_connect/group_link: - oidc_group_name: OpenID group identifier + oidc_group_name: Mã định danh nhóm OpenID openid_connect/provider: - name: Tên - slug: Unique identifier + name: tên + slug: Mã định danh duy nhất display_name: Tên hiển thị - client_id: ID người dùng - client_secret: Khóa bí mật người dùng - groups_claim: Groups claim - group_regexes: Patterns (regular expressions) - secret: Bí mật - scope: Phạm vi - sync_groups: Synchronize groups - grant_types_supported: Loại hỗ trợ - limit_self_registration: Giới hạn đăng ký tự động + client_id: ID khách hàng + client_secret: Bí mật của khách hàng + groups_claim: Nhóm yêu cầu + group_regexes: Mẫu (biểu thức chính quy) + secret: bí mật + scope: phạm vi + sync_groups: Đồng bộ hóa nhóm + grant_types_supported: Các loại trợ cấp được hỗ trợ + limit_self_registration: Hạn chế tự đăng ký authorization_endpoint: Điểm cuối ủy quyền - userinfo_endpoint: Thông tin điểm cuối người dùng - token_endpoint: Mã khóa điểm cuối - end_session_endpoint: Kết phiên điểm cuối - post_logout_redirect_uri: URI chuyển hướng sau đăng xuất - jwks_uri: JWKS URI - issuer: Nhà cung cấp - tenant: Thuê bao - metadata_url: Siêu dữ liệu URL + userinfo_endpoint: Điểm cuối thông tin người dùng + token_endpoint: Điểm cuối mã thông báo + end_session_endpoint: Điểm cuối phiên kết thúc + post_logout_redirect_uri: URI chuyển hướng đăng xuất + jwks_uri: URI JWKS + issuer: bên phát hành + tenant: Tenant + metadata_url: URL siêu dữ liệu icon: Biểu tượng tùy chỉnh - claims: Tuyên bố + claims: Khiếu nại acr_values: Giá trị ACR - redirect_url: Redirect URL + redirect_url: URL chuyển hướng errors: models: openid_connect/provider: attributes: metadata_url: - format: "Khám phá URL điểm cuối %{message}" - response_is_not_successful: " phản hồi với %{status}." - response_is_not_json: " không trả về nội dung JSON." - response_misses_required_attributes: " không trả về thuộc tính cần có. Các thuộc tính bị thiếu là: %{missing_attributes}." - invalid_claims_essential: "does not define a boolean at %{attribute}." - invalid_claims_location: "contain unsupported locations: %{invalid}. Supported locations are: %{supported}." - invalid_claims_values: "does not define an array at %{attribute}." - non_object_attribute: "does not define a JSON object at %{attribute}." + format: "URL điểm cuối khám phá %{message}" + response_is_not_successful: "phản hồi bằng %{status}." + response_is_not_json: "không trả về phần thân JSON." + response_misses_required_attributes: "không trả về các thuộc tính bắt buộc. Các thuộc tính bị thiếu là: %{missing_attributes}." + invalid_claims_essential: "không xác định boolean tại %{attribute}." + invalid_claims_location: "chứa các vị trí không được hỗ trợ: %{invalid}. Các địa điểm được hỗ trợ là: %{supported}." + invalid_claims_values: "không xác định mảng tại %{attribute}." + non_object_attribute: "không xác định đối tượng JSON tại %{attribute}." provider: delete_warning: input_delete_confirmation: Nhập tên nhà cung cấp %{name} để xác nhận xóa. - irreversible_notice: Deleting an SSO provider is an irreversible action. - provider: 'Are you sure you want to delete the SSO provider %{name}? To confirm this action please enter the name of the provider in the field below, this will:' - delete_result_1: Remove the provider from the list of available providers. + irreversible_notice: Xóa nhà cung cấp SSO là hành động không thể thay đổi được. + provider: 'Bạn có chắc chắn muốn xóa nhà cung cấp SSO %{name} không? Để xác nhận hành động này, vui lòng nhập tên của nhà cung cấp vào trường bên dưới, điều này sẽ:' + delete_result_1: Xóa nhà cung cấp khỏi danh sách các nhà cung cấp có sẵn. delete_result_user_count: - zero: No users are currently using this provider. No further action is required. - one: "One user is currently still using this provider. They will need to be re-invited or logging in with another provider." - other: "%{count} users are currently still using this provider. They will need to be re-invited or logging in with another provider." - delete_result_direct: This provider is marked as a direct login provider. The setting will be removed and users will no longer be redirected to the provider for login. + zero: Không có người dùng hiện đang sử dụng nhà cung cấp này. Không cần thực hiện thêm hành động nào. + one: "Một người dùng hiện vẫn đang sử dụng nhà cung cấp này. Họ sẽ cần được mời lại hoặc đăng nhập với nhà cung cấp khác." + other: "%{count} người dùng hiện vẫn đang sử dụng nhà cung cấp này. Họ sẽ cần được mời lại hoặc đăng nhập với nhà cung cấp khác." + delete_result_direct: Nhà cung cấp này được đánh dấu là nhà cung cấp đăng nhập trực tiếp. Cài đặt này sẽ bị xóa và người dùng sẽ không được chuyển hướng đến nhà cung cấp để đăng nhập nữa. openid_connect: - menu_title: Các nhà cung cấp OpenID - delete_title: "Delete OpenID Connect provider" - group_links_heading: OpenID Connect group links + menu_title: Nhà cung cấp OpenID + delete_title: "Xóa nhà cung cấp OpenID Connect" + group_links_heading: Liên kết nhóm OpenID Connect groups: match_preview_component: - title: Extracted group names - placeholder_none: No groups currently match the pattern. + title: Tên nhóm được trích xuất + placeholder_none: Hiện tại không có nhóm nào phù hợp với mẫu này. match_preview_dialog_component: - description: This allows you to test your regular expression pattern against sample data to verify that the group names you want extracted are indeed extracted properly. You can modify your pattern here but the test string and matches themselves will not be saved with the configuration. - group_names_label: Test group names - group_names_description: Enter one group name per line as it would appear in the OIDC claim, to see how your regular expression patterns affect them. - show_button: Test pattern - title: Test regular expressions + description: Điều này cho phép bạn kiểm tra mẫu biểu thức chính quy của mình dựa trên dữ liệu mẫu để xác minh rằng tên nhóm bạn muốn trích xuất thực sự được trích xuất đúng cách. Bạn có thể sửa đổi mẫu của mình tại đây nhưng chuỗi thử nghiệm và các kết quả khớp sẽ không được lưu cùng với cấu hình. + group_names_label: Tên nhóm thử nghiệm + group_names_description: Nhập một tên nhóm trên mỗi dòng giống như tên xuất hiện trong xác nhận quyền sở hữu OIDC để xem các mẫu biểu thức chính quy của bạn ảnh hưởng đến chúng như thế nào. + show_button: Mẫu thử nghiệm + title: Kiểm tra biểu thức chính quy instructions: - endpoint_url: The endpoint URL given to you by the OpenID Connect provider - group_regexes: One regular expression pattern per line. Please read [the documentation](docs_url) for more information on how to do this. - group_regexes_detail: Optionally define patterns using regular expressions to match group names from the groups claim. For example, entering a pattern like ^Level1/Level2/([\w/]+)$ will extract the group “Groupname/SubGroup” from a response including “Level1/Level2/GroupName/SubGroup”. If no pattern matches, the group is ignored. Leave empty to synchronize all groups using their full group name. - group_regexes_testing: "Click on 'Test pattern' to test your regular expression pattern against sample OIDC userinfo." - group_sync: Automatically extract and synchronise group membership information from the identity provider. - groups_claim: Specify the name of the claim that's expected to indicate the group names that the user is a member of. - metadata_none: I don't have this information - metadata_url: I have a discovery endpoint URL - oidc_information: These values are needed to configure the OpenID Connect provider. - client_id: This is the client ID given to you by your OpenID Connect provider - client_secret: This is the client secret given to you by your OpenID Connect provider - 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. - tenant: 'Please replace the default tenant with your own if applicable. See this.' - 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). - post_logout_redirect_uri: The URL the OpenID Connect provider should redirect to after a logout request. + endpoint_url: URL điểm cuối do nhà cung cấp OpenID Connect cung cấp cho bạn + group_regexes: Một mẫu biểu thức chính quy trên mỗi dòng. Vui lòng đọc [the documentation](docs_url) để biết thêm thông tin về cách thực hiện việc này. + group_regexes_detail: 'Tùy ý xác định các mẫu bằng cách sử dụng biểu thức chính quy để khớp với tên nhóm từ yêu cầu của nhóm. Ví dụ: nhập mẫu như ^Level1/Level2/([\w/]+)$ sẽ trích xuất nhóm “Tên nhóm/Nhóm con” từ một phản hồi bao gồm “Cấp1/Cấp2/Tên nhóm/Nhóm con”. Nếu không có mẫu nào khớp, nhóm sẽ bị bỏ qua. Để trống để đồng bộ hóa tất cả các nhóm bằng tên nhóm đầy đủ của họ.' + group_regexes_testing: "Nhấp vào 'Mẫu thử nghiệm' để kiểm tra mẫu biểu thức chính quy của bạn dựa trên thông tin người dùng OIDC mẫu." + group_sync: Tự động trích xuất và đồng bộ hóa thông tin thành viên nhóm từ nhà cung cấp danh tính. + groups_claim: Chỉ định tên của xác nhận quyền sở hữu dự kiến ​​sẽ cho biết tên nhóm mà người dùng là thành viên. + metadata_none: Tôi không có thông tin này + metadata_url: Tôi có URL điểm cuối khám phá + oidc_information: Những giá trị này là cần thiết để định cấu hình nhà cung cấp OpenID Connect. + client_id: Đây là ID khách hàng do nhà cung cấp OpenID Connect của bạn cấp cho bạn + client_secret: Đây là bí mật ứng dụng khách được nhà cung cấp OpenID Connect cung cấp cho bạn + limit_self_registration: Nếu được bật, người dùng chỉ có thể đăng ký sử dụng nhà cung cấp này nếu cấu hình phía nhà cung cấp cho phép. + display_name: Tên của nhà cung cấp. Điều này sẽ được hiển thị dưới dạng nút đăng nhập và trong danh sách các nhà cung cấp. + tenant: 'Vui lòng thay thế đối tượng thuê mặc định bằng đối tượng thuê của riêng bạn nếu có. Xem this.' + scope: Nếu muốn yêu cầu phạm vi tùy chỉnh, bạn có thể thêm một hoặc nhiều giá trị phạm vi, phân tách bằng dấu cách tại đây. Để biết thêm thông tin, hãy xem [OpenID Connect documentation](docs_url). + post_logout_redirect_uri: URL mà nhà cung cấp OpenID Connect sẽ chuyển hướng đến sau khi có yêu cầu đăng xuất. claims: > - You can request additional claims for the userinfo and id token endpoints. Please see [our OpenID connect documentation](docs_url) for more information. + Bạn có thể yêu cầu xác nhận quyền sở hữu bổ sung cho điểm cuối thông tin người dùng và mã thông báo id. Vui lòng xem [our OpenID connect documentation](docs_url) để biết thêm thông tin. acr_values: > - Request non-essential claims in an easier format. See [our documentation on acr_values](docs_url) for more information. + Yêu cầu các yêu cầu không cần thiết ở dạng dễ dàng hơn. Xem [our documentation on acr_values](docs_url) để biết thêm thông tin. mapping_login: > - Provide a custom mapping in the userinfo response to be used for the login attribute. + Cung cấp ánh xạ tùy chỉnh trong phản hồi thông tin người dùng để sử dụng cho thuộc tính đăng nhập. mapping_email: > - Provide a custom mapping in the userinfo response to be used for the email attribute. + Cung cấp ánh xạ tùy chỉnh trong phản hồi thông tin người dùng để sử dụng cho thuộc tính email. mapping_first_name: > - Provide a custom mapping in the userinfo response to be used for the first name. + Cung cấp ánh xạ tùy chỉnh trong phản hồi thông tin người dùng để sử dụng cho tên. mapping_last_name: > - Provide a custom mapping in the userinfo response to be used for the last name. + Cung cấp ánh xạ tùy chỉnh trong phản hồi thông tin người dùng để sử dụng cho họ. mapping_admin: > - Provide a custom mapping in the userinfo response to be used for the admin status. It expects a boolean attribute to be returned. + Cung cấp ánh xạ tùy chỉnh trong phản hồi thông tin người dùng để sử dụng cho trạng thái quản trị viên. Nó mong đợi một thuộc tính boolean được trả về. settings: - metadata_none: I don't have this information - metadata_url: I have a discovery endpoint URL - endpoint_url: Endpoint URL + metadata_none: Tôi không có thông tin này + metadata_url: Tôi có URL điểm cuối khám phá + endpoint_url: URL điểm cuối providers: - label_providers: "Providers" - seeded_from_env: "This provider was seeded from the environment configuration. It cannot be edited." + label_providers: "nhà cung cấp" + seeded_from_env: "Nhà cung cấp này được chọn từ cấu hình môi trường. Nó không thể được chỉnh sửa." google: name: Google microsoft_entra: name: Microsoft Entra custom: - name: Custom + name: tùy chỉnh upsell: - title: "Single Sign-On (SSO) with OpenID connect" - description: Connect OpenProject to an OpenID connect identity provider + title: "Đăng nhập một lần (SSO) với kết nối OpenID" + description: Kết nối OpenProject với nhà cung cấp nhận dạng kết nối OpenID label_add_new: Thêm nhà cung cấp OpenID mới label_edit: Chỉnh sửa nhà cung cấp OpenID %{name} - label_empty_title: No OpenID providers configured yet. - label_empty_description: Add a provider to see them here. - label_group_mapping: Group mapping - label_metadata: OpenID Connect Discovery Endpoint - label_automatic_configuration: Automatic configuration - label_optional_configuration: Optional configuration - label_advanced_configuration: Advanced configuration - label_configuration_details: Metadata - label_client_details: Client details + label_empty_title: Chưa có nhà cung cấp OpenID nào được định cấu hình. + label_empty_description: Thêm nhà cung cấp để xem chúng ở đây. + label_group_mapping: Ánh xạ nhóm + label_metadata: Điểm cuối khám phá kết nối OpenID + label_automatic_configuration: Cấu hình tự động + label_optional_configuration: Cấu hình tùy chọn + label_advanced_configuration: Cấu hình nâng cao + label_configuration_details: Siêu dữ liệu + label_client_details: Chi tiết khách hàng label_attribute_mapping: Ánh xạ thuộc tính - notice_created: A new OpenID provider was successfully created. - client_details_description: Configuration details of OpenProject as an OIDC client - no_results_table: Chưa có nhà cung cấp nào được định nghĩa. - plural: Các nhà cung cấp OpenID + notice_created: Nhà cung cấp OpenID mới đã được tạo thành công. + client_details_description: Chi tiết cấu hình của OpenProject với tư cách là máy khách OIDC + no_results_table: Chưa có nhà cung cấp nào được xác định. + plural: Nhà cung cấp OpenID singular: Nhà cung cấp OpenID section_texts: - group_mapping: Map group names provided by the identity provider to groups in OpenProject. - metadata: Pre-fill configuration using an OpenID Connect discovery endpoint URL - metadata_form_banner: Editing the discovery endpoint may override existing pre-filled metadata values. - metadata_form_title: OpenID Connect Discovery endpoint - metadata_form_description: If your identity provider has a discovery endpoint URL. Use it below to pre-fill configuration. - configuration_metadata: The information has been pre-filled using the supplied discovery endpoint. In most cases, they do not require editing. - configuration: Configuration details of the OpenID Connect provider - display_name: The display name visible to users. - attribute_mapping: Configure the mapping of attributes between OpenProject and the OpenID Connect provider. - claims: Request additional claims for the ID token or userinfo response. + group_mapping: Ánh xạ tên nhóm do nhà cung cấp danh tính cung cấp cho các nhóm trong OpenProject. + metadata: Điền trước cấu hình bằng URL điểm cuối khám phá OpenID Connect + metadata_form_banner: Việc chỉnh sửa điểm cuối khám phá có thể ghi đè các giá trị siêu dữ liệu được điền sẵn hiện có. + metadata_form_title: Điểm cuối khám phá kết nối OpenID + metadata_form_description: Nếu nhà cung cấp danh tính của bạn có URL điểm cuối khám phá. Sử dụng nó bên dưới để điền trước cấu hình. + configuration_metadata: Thông tin đã được điền trước bằng cách sử dụng điểm cuối khám phá được cung cấp. Trong hầu hết các trường hợp, chúng không yêu cầu chỉnh sửa. + configuration: Chi tiết cấu hình của nhà cung cấp OpenID Connect + display_name: Tên hiển thị hiển thị cho người dùng. + attribute_mapping: Định cấu hình ánh xạ các thuộc tính giữa OpenProject và nhà cung cấp OpenID Connect. + claims: Yêu cầu xác nhận quyền sở hữu bổ sung cho mã thông báo ID hoặc phản hồi thông tin người dùng. side_panel: information_component: - backchannel_logout_url: Backchannel logout URL + backchannel_logout_url: URL đăng xuất kênh sau setting_instructions: limit_self_registration: > - Nếu được bật, người dùng chỉ có thể đăng ký bằng nhà cung cấp này nếu cài đặt đăng ký tự động cho phép. + Nếu được bật, người dùng chỉ có thể đăng ký bằng nhà cung cấp này nếu cài đặt tự đăng ký cho phép. diff --git a/modules/overviews/config/locales/crowdin/js-vi.yml b/modules/overviews/config/locales/crowdin/js-vi.yml index 02840baa2aa..58dcf4ba495 100644 --- a/modules/overviews/config/locales/crowdin/js-vi.yml +++ b/modules/overviews/config/locales/crowdin/js-vi.yml @@ -1,4 +1,4 @@ vi: js: overviews: - label: 'Tổng quan' + label: 'tổng quan' diff --git a/modules/overviews/config/locales/crowdin/vi.yml b/modules/overviews/config/locales/crowdin/vi.yml index 068decc4962..6e19b5f43a7 100644 --- a/modules/overviews/config/locales/crowdin/vi.yml +++ b/modules/overviews/config/locales/crowdin/vi.yml @@ -1,5 +1,5 @@ vi: overviews: - label_home: "%{workspace_type} home" - label_dashboard: "Bảng điều khiển" - label_overview: "Tổng quan" + label_home: "%{workspace_type} nhà" + label_dashboard: "Trang tổng quan" + label_overview: "tổng quan" diff --git a/modules/recaptcha/config/locales/crowdin/vi.yml b/modules/recaptcha/config/locales/crowdin/vi.yml index 853bef1f2b3..cad6cb6052c 100644 --- a/modules/recaptcha/config/locales/crowdin/vi.yml +++ b/modules/recaptcha/config/locales/crowdin/vi.yml @@ -2,24 +2,24 @@ vi: plugin_openproject_recaptcha: name: "OpenProject ReCaptcha" - description: "Mô-đun này cung cấp các kiểm tra recaptcha trong quá trình đăng nhập." + description: "Mô-đun này cung cấp khả năng kiểm tra recaptcha trong quá trình đăng nhập." recaptcha: label_recaptcha: "reCAPTCHA" button_please_wait: 'Vui lòng chờ ...' verify_account: "Xác minh tài khoản của bạn" error_captcha: "Tài khoản của bạn không thể được xác minh. Vui lòng liên hệ với quản trị viên." settings: - website_key: 'Khóa truy cập trang (Hay còn được gọi là khóa trang)' + website_key: 'Khóa trang web (Cũng có thể được gọi là "Khóa trang web")' response_limit: 'Giới hạn phản hồi cho HCaptcha' response_limit_text: 'Số ký tự tối đa để coi phản hồi HCaptcha là hợp lệ.' - website_key_text: 'Nhập khóa website mà bạn đã tạo trên bảng điều khiển quản trị reCAPTCHA cho miền này.' + website_key_text: 'Nhập khóa trang web bạn đã tạo trên bảng điều khiển dành cho quản trị viên reCAPTCHA cho miền này.' secret_key: 'Khóa bí mật' - secret_key_text: 'Nhập khóa bí mật mà bạn đã tạo trên bảng điều khiển quản trị reCAPTCHA.' + secret_key_text: 'Nhập khóa bí mật bạn đã tạo trên bảng điều khiển dành cho quản trị viên reCAPTCHA.' type: 'Sử dụng reCAPTCHA' type_disabled: 'Tắt reCAPTCHA' type_v2: 'reCAPTCHA v2' type_v3: 'reCAPTCHA v3' type_hcaptcha: 'HCaptcha' - type_turnstile: 'Turnstile™ của Cloudflare' + type_turnstile: 'Cửa quay Cloudflare™' captcha_description_html: > - reCAPTCHA là dịch vụ miễn phí của Google có thể được bật cho phiên bản OpenProject của bạn. Nếu được bật, biểu mẫu captcha sẽ được hiển thị khi đăng nhập cho tất cả người dùng chưa xác minh captcha.
    Vui lòng xem liên kết sau để biết thêm chi tiết về reCAPTCHA và các phiên bản của chúng, cũng như cách tạo trang web và khóa bí mật: %{recaptcha_link}
    HCaptcha là giải pháp thay thế miễn phí của Google mà bạn có thể sử dụng nếu không muốn sử dụng reCAPTCHA. Xem liên kết này để biết thêm thông tin: %{hcaptcha_link}
    Cloudflare Turnstile™ là một giải pháp thay thế khác tiện lợi hơn cho người dùng nhưng vẫn cung cấp cùng mức độ bảo mật. Xem liên kết này để biết thêm thông tin: %{turnstile_link} + reCAPTCHA là một dịch vụ miễn phí của Google có thể được kích hoạt cho phiên bản OpenProject của bạn. Nếu được bật, biểu mẫu hình ảnh xác thực sẽ được hiển thị khi đăng nhập cho tất cả người dùng chưa xác minh hình ảnh xác thực.
    Vui lòng xem liên kết sau để biết thêm chi tiết về reCAPTCHA và các phiên bản của chúng cũng như cách tạo trang web và khóa bí mật: %{recaptcha_link}
    HCaptcha là giải pháp thay thế không có Google mà bạn có thể sử dụng nếu không muốn sử dụng reCAPTCHA. Xem liên kết này để biết thêm thông tin: %{hcaptcha_link}
    Cloudflare Turnstile™ là một giải pháp thay thế khác thuận tiện hơn cho người dùng trong khi vẫn cung cấp cùng mức độ bảo mật. Xem liên kết này để biết thêm thông tin: %{turnstile_link} diff --git a/modules/reporting/config/locales/crowdin/js-vi.yml b/modules/reporting/config/locales/crowdin/js-vi.yml index 57b73c90403..9aa428c54fe 100644 --- a/modules/reporting/config/locales/crowdin/js-vi.yml +++ b/modules/reporting/config/locales/crowdin/js-vi.yml @@ -22,5 +22,5 @@ vi: js: reporting_engine: - label_remove: "Xoá" + label_remove: "xóa" label_response_error: "Đã xảy ra lỗi khi xử lý truy vấn." diff --git a/modules/reporting/config/locales/crowdin/vi.yml b/modules/reporting/config/locales/crowdin/vi.yml index bdbbbdf9f4e..43bd0a017d5 100644 --- a/modules/reporting/config/locales/crowdin/vi.yml +++ b/modules/reporting/config/locales/crowdin/vi.yml @@ -21,86 +21,86 @@ #++ vi: plugin_openproject_reporting: - name: "OpenProject Reporting" - description: "Mô-đun này cho phép tạo các báo cáo chi phí tùy chỉnh với việc lọc và nhóm dữ liệu do mô-đun Thời gian và chi phí của OpenProject tạo ra." + name: "Báo cáo dự án mở" + description: "Plugin này cho phép tạo báo cáo chi phí tùy chỉnh với tính năng lọc và nhóm được tạo bởi plugin Thời gian và chi phí OpenProject." button_save_report_as: "Lưu báo cáo dưới dạng..." - comments: "Nhận xét" + comments: "bình luận" cost_reports_title: "Thời gian và chi phí" label_cost_report: "Báo cáo chi phí" label_cost_report_plural: "Báo cáo chi phí" description_drill_down: "Hiển thị chi tiết" - description_filter_selection: "Lựa chọn" - description_multi_select: "Hiển thị đa lựa chọn" - description_remove_filter: "Gỡ bỏ bộ lọc" + description_filter_selection: "lựa chọn" + description_multi_select: "Hiển thị nhiều lựa chọn" + description_remove_filter: "Xóa bộ lọc" information_restricted_depending_on_permission: "Tùy thuộc vào quyền của bạn, trang này có thể chứa thông tin bị hạn chế." - label_click_to_edit: "Nhấp để chỉnh sửa." - label_closed: "đã đóng" + label_click_to_edit: "Bấm để chỉnh sửa." + label_closed: "đóng cửa" label_columns: "Cột" - label_cost_entry_attributes: "Các thuộc tính mục chi phí" - label_days_ago: "trong những ngày gần đây" - label_entry: "Mục chi phí" - label_filter_text: "Văn bản bộ lọc" - label_filter_value: "Giá trị" - label_filters: "Bộ lọc" + label_cost_entry_attributes: "Thuộc tính mục nhập chi phí" + label_days_ago: "trong những ngày qua" + label_entry: "Nhập chi phí" + label_filter_text: "Lọc văn bản" + label_filter_value: "giá trị" + label_filters: "bộ lọc" label_greater: ">" - label_is_not_project_with_subprojects: "không phải (bao gồm các dự án con)" - label_is_project_with_subprojects: "là (bao gồm các dự án con)" - label_is_work_package_with_descendants: "là (bao gồm tập con)" - label_is_not_work_package_with_descendants: "không phải (kể cả tập con)" - label_work_package_attributes: "Các thuộc tính gói công việc" + label_is_not_project_with_subprojects: "không (bao gồm các tiểu dự án)" + label_is_project_with_subprojects: "là (bao gồm các tiểu dự án)" + label_is_work_package_with_descendants: "là (bao gồm con cháu)" + label_is_not_work_package_with_descendants: "không phải (bao gồm cả con cháu)" + label_work_package_attributes: "Thuộc tính gói công việc" label_less: "<" - label_logged_by_reporting: "Đã ghi bởi" - label_money: "Giá trị tiền tệ" + label_logged_by_reporting: "Đăng nhập bởi" + label_money: "Giá trị tiền mặt" label_month_reporting: "Tháng (Chi tiêu)" label_new_report: "Báo cáo chi phí mới" label_open: "mở" label_operator: "Toán tử" - label_private_report_plural: "Các báo cáo chi phí cá nhân" + label_private_report_plural: "Báo cáo chi phí riêng" label_progress_bar_explanation: "Đang tạo báo cáo..." - label_public_report_plural: "Các báo cáo chi phí công khai" - label_really_delete_question: "Bạn có chắc chắn muốn xóa báo cáo này không?" - label_rows: "Hàng" + label_public_report_plural: "Báo cáo chi phí công" + label_really_delete_question: "Bạn có chắc chắn muốn xóa báo cáo này?" + label_rows: "hàng" label_saving: "Đang lưu ..." - label_spent_on_reporting: "Ngày (Chi tiêu)" - label_sum: "Tổng" - label_units: "Đơn vị" - label_week_reporting: "Tuần (Chi tiêu)" + label_spent_on_reporting: "Ngày (Đã chi tiêu)" + label_sum: "tổng hợp" + label_units: "đơn vị" + label_week_reporting: "Tuần (Đã chi tiêu)" label_year_reporting: "Năm (Chi tiêu)" - label_count: "Số lượng" - label_filter: "Bộ lọc" - label_filter_add: "Thêm Bộ lọc" + label_count: "đếm" + label_filter: "bộ lọc" + label_filter_add: "Thêm bộ lọc" label_filter_plural: "Bộ lọc" label_group_by: "Nhóm theo" - label_group_by_add: "Thêm thuộc tính nhóm theo" + label_group_by_add: "Thêm thuộc tính theo nhóm" label_inactive: "«không hoạt động»" label_no: "Không" label_none: "(không có dữ liệu)" - label_no_reports: "Chưa có báo cáo chi phí nào." + label_no_reports: "Chưa có báo cáo chi phí." label_report: "Báo cáo" label_yes: "Có" - load_query_question: "Báo cáo sẽ có %{size} ô bảng và có thể mất một chút thời gian để tạo ra. Bạn có muốn thử tạo báo cáo không?" - permission_save_cost_reports: "Lưu các báo cáo chi phí công khai" - permission_save_private_cost_reports: "Lưu các báo cáo chi phí cá nhân" + load_query_question: "Báo cáo sẽ có %{size} ô bảng và có thể mất chút thời gian để hiển thị. Bạn vẫn muốn thử kết xuất nó chứ?" + permission_save_cost_reports: "Lưu báo cáo chi phí công" + permission_save_private_cost_reports: "Lưu báo cáo chi phí riêng" project_module_reporting_module: "Các báo cáo chi phí" - text_costs_are_rounded_note: "Các giá trị hiển thị được làm tròn. Tất cả các phép tính dựa trên các giá trị chưa được làm tròn." - toggle_multiselect: "kích hoạt/tắt đa lựa chọn" - units: "Đơn vị" + text_costs_are_rounded_note: "Các giá trị hiển thị được làm tròn. Tất cả các tính toán đều dựa trên các giá trị không làm tròn." + toggle_multiselect: "kích hoạt/hủy kích hoạt đa lựa chọn" + units: "đơn vị" validation_failure_date: "không phải là một ngày hợp lệ" - validation_failure_integer: "không phải là một số nguyên hợp lệ" + validation_failure_integer: "không phải là số nguyên hợp lệ" export: timesheet: - title: "Xuất bảng thời gian của bạn bằng PDF" - button: "Xuất bảng thời gian bằng PDF" - timesheet: "Bảng thời gian" - time: "Thời gian" - sums_hours: Sums - overview_per_user_total: "Overview: Total hours per user" - overview_per_user_day: "Overview: Hours per user per day" + title: "Xuất bảng chấm công PDF của bạn" + button: "Xuất bảng chấm công PDF" + timesheet: "bảng chấm công" + time: "thời gian" + sums_hours: Tổng + overview_per_user_total: "Tổng quan: Tổng số giờ trên mỗi người dùng" + overview_per_user_day: "Tổng quan: Số giờ mỗi người dùng mỗi ngày" cost_reports: - title: "Xuất báo cáo chi phí của bạn sang XLS" + title: "Xuất báo cáo chi phí XLS của bạn" start_time: "Thời gian bắt đầu" end_time: "Thời gian kết thúc" reporting: group_by: - selected_columns: "Các cột đã chọn" - selected_rows: "Các hàng đã chọn" + selected_columns: "Cột đã chọn" + selected_rows: "Hàng đã chọn" diff --git a/modules/storages/config/locales/crowdin/fr.yml b/modules/storages/config/locales/crowdin/fr.yml index 361d88b785c..d858daae69b 100644 --- a/modules/storages/config/locales/crowdin/fr.yml +++ b/modules/storages/config/locales/crowdin/fr.yml @@ -104,20 +104,20 @@ fr: create_folder: 'Création du dossier du projet géré :' ensure_root_folder_permissions: 'Définir les autorisations du dossier de base :' hide_inactive_folders: 'Masquer l''étape des dossiers inactifs :' - remote_folders: 'Read contents of the team folder:' + remote_folders: 'Lisez le contenu du dossier de l''équipe :' remove_user_from_group: 'Retirer l''utilisateur de ce groupe :' rename_project_folder: 'Renommer le dossier du projet géré :' one_drive_sync_service: create_folder: 'Création du dossier du projet géré :' ensure_root_folder_permissions: 'Définir les autorisations du dossier de base :' hide_inactive_folders: 'Masquer l''étape des dossiers inactifs :' - remote_folders: 'Read contents of the drive root folder:' + remote_folders: 'Lire le contenu du dossier racine du lecteur :' rename_project_folder: 'Renommer le dossier du projet géré :' sharepoint_sync_service: create_folder: 'Création du dossier du projet géré :' ensure_root_folder_permissions: 'Définir les autorisations du dossier de base :' hide_inactive_folders: 'Masquer l''étape des dossiers inactifs :' - remote_folders: 'Read contents of the drive root folder:' + remote_folders: 'Lire le contenu du dossier racine du lecteur :' rename_project_folder: 'Renommer le dossier du projet géré :' errors: messages: @@ -140,7 +140,7 @@ fr: conflict: Le dossier %{folder_name} existe déjà dans %{parent_location}. not_found: "%{parent_location} n'a pas été trouvé." ensure_root_folder_permissions: - not_found: "%{group_folder} wasn't found. Please check your Nextcloud Team Folder setup." + not_found: "%{group_folder} n'a pas été trouvé. Veuillez vérifier la configuration de votre dossier d'équipe Nextcloud." permission_not_set: n'a pas pu définir les autorisations sur %{group_folder}. hide_inactive_folders: permission_not_set: n'a pas pu définir les autorisations sur %{path}. @@ -230,7 +230,7 @@ fr: storage_delete_result_3: Le dossier du projet géré automatiquement et tous les fichiers qu'il contient seront supprimés. dependencies: nextcloud: - group_folders_app: Team Folders + group_folders_app: Dossiers d'équipe integration_app: Intégration OpenProject enabled_in_projects: setup_incomplete_description: La configuration de cet espace de stockage est incomplète. Veuillez compléter la configuration avant de l'activer dans plusieurs projets. @@ -277,11 +277,11 @@ fr: client_folder_creation: Création automatique de dossier client_folder_removal: Suppression automatique de dossier drive_contents: Contenu du lecteur - files_request: Fetching team folder files + files_request: Récupération des fichiers du dossier de l'équipe header: Dossiers de projets gérés automatiquement - team_folder_app: 'Dependency: Team Folders' - team_folder_contents: Team folder content - team_folder_presence: Team folder exists + team_folder_app: 'Dépendance : dossiers d''équipe' + team_folder_contents: Contenu du dossier de l'équipe + team_folder_presence: Le dossier de l'équipe existe userless_access: Authentification de la requête côté serveur authentication: existing_token: Jeton d'utilisateur @@ -322,8 +322,8 @@ fr: nc_oauth_request_not_found: Le point de terminaison pour récupérer l'utilisateur actuellement connecté n'a pas été trouvé. Veuillez vérifier les journaux du serveur pour obtenir plus d'informations. nc_oauth_request_unauthorized: L'utilisateur actuel n'est pas autorisé à accéder à l'espace de stockage de fichiers distant. Veuillez consulter les journaux du serveur pour obtenir plus d'informations. nc_oauth_token_missing: OpenProject ne peut pas tester la communication au niveau utilisateur avec Nextcloud, car l'utilisateur n'a pas encore lié son compte Nextcloud. - nc_team_folder_not_found: The team folder could not be found. - nc_unexpected_content: Unexpected content found in the managed team folder. + nc_team_folder_not_found: Le dossier de l'équipe est introuvable. + nc_unexpected_content: Contenu inattendu trouvé dans le dossier d'équipe géré. nc_userless_access_denied: Le mot de passe de l'application configurée n'est pas valide. not_configured: La connexion n'a pas pu être validée. Veuillez d'abord terminer la configuration. od_client_cant_delete_folder: Le client rencontre des difficultés pour supprimer des dossiers. Veuillez consulter la documentation d'installation de votre espace de stockage. diff --git a/modules/storages/config/locales/crowdin/it.yml b/modules/storages/config/locales/crowdin/it.yml index 17800c3fe6d..93567eeaa53 100644 --- a/modules/storages/config/locales/crowdin/it.yml +++ b/modules/storages/config/locales/crowdin/it.yml @@ -104,20 +104,20 @@ it: create_folder: 'Creazione di cartelle di progetto gestite:' ensure_root_folder_permissions: 'Imposta le autorizzazioni della cartella base:' hide_inactive_folders: 'Nascondi il passaggio delle cartelle inattive:' - remote_folders: 'Read contents of the team folder:' + remote_folders: 'Leggi i contenuti della cartella di team:' remove_user_from_group: 'Rimuovi Utenti dal Gruppo:' rename_project_folder: 'Rinomina la cartella del progetto gestita:' one_drive_sync_service: create_folder: 'Creazione della cartella di progetto gestita:' ensure_root_folder_permissions: 'Imposta le autorizzazioni della cartella base:' hide_inactive_folders: 'Nascondi il passaggio delle cartelle inattive:' - remote_folders: 'Read contents of the drive root folder:' + remote_folders: 'Leggi il contenuto della cartella radice dell''unità:' rename_project_folder: 'Rinomina la cartella di progetto gestita:' sharepoint_sync_service: create_folder: 'Creazione della cartella di progetto gestita:' ensure_root_folder_permissions: 'Imposta le autorizzazioni della cartella base:' hide_inactive_folders: 'Nascondi il passaggio delle cartelle inattive:' - remote_folders: 'Read contents of the drive root folder:' + remote_folders: 'Leggi il contenuto della cartella radice dell''unità:' rename_project_folder: 'Rinomina la cartella di progetto gestita:' errors: messages: @@ -140,7 +140,7 @@ it: conflict: La cartella %{folder_name} esiste già su %{parent_location}. not_found: "Impossibile trovare %{parent_location}." ensure_root_folder_permissions: - not_found: "%{group_folder} wasn't found. Please check your Nextcloud Team Folder setup." + not_found: "Impossibile trovare %{group_folder}. Verifica le impostazioni della cartella di team Nextcloud." permission_not_set: impossibile impostare i permessi su %{group_folder}. hide_inactive_folders: permission_not_set: impossibile impostare i permessi su %{path}. @@ -230,7 +230,7 @@ it: storage_delete_result_3: La cartella di progetto gestita automaticamente e tutti i file in essa contenuti verranno eliminati dependencies: nextcloud: - group_folders_app: Team Folders + group_folders_app: Cartelle di team integration_app: Integration OpenProject enabled_in_projects: setup_incomplete_description: Questo archivio ha una configurazione incompleta. Completa la configurazione prima di abilitarlo in più progetti. @@ -277,11 +277,11 @@ it: client_folder_creation: Creazione automatica delle cartelle client_folder_removal: Eliminazione automatica delle cartelle drive_contents: Contenuto dell'unità - files_request: Fetching team folder files + files_request: Recupero dei file delle cartelle di team header: Cartelle di progetto gestite automaticamente - team_folder_app: 'Dependency: Team Folders' - team_folder_contents: Team folder content - team_folder_presence: Team folder exists + team_folder_app: 'Dipendenza: Cartelle di team' + team_folder_contents: Contenuto della cartella di team + team_folder_presence: La cartella di team esiste userless_access: Autenticazione richiesta lato server authentication: existing_token: Token utente @@ -322,8 +322,8 @@ it: nc_oauth_request_not_found: L'endpoint per recuperare l'utente attualmente connesso non è stato trovato. Per ulteriori informazioni, consulta i log del server. nc_oauth_request_unauthorized: L'utente attuale non è autorizzato ad accedere all'archivio file remoto. Per ulteriori informazioni, consulta i log del server. nc_oauth_token_missing: OpenProject non può testare la comunicazione a livello utente con Nextcloud poiché l'utente non ha ancora collegato il proprio account Nextcloud. - nc_team_folder_not_found: The team folder could not be found. - nc_unexpected_content: Unexpected content found in the managed team folder. + nc_team_folder_not_found: Impossibile trovare la cartella di team. + nc_unexpected_content: Contenuto inatteso trovato nella cartella di team gestita. nc_userless_access_denied: La password dell'app configurata non è valida. not_configured: Non è stato possibile verificare la connessione. Prima è necessario completare la configurazione. od_client_cant_delete_folder: Il cliente riscontra problemi con l'eliminazione delle cartelle. Consulta la documentazione di configurazione del tuo archivio. diff --git a/modules/storages/config/locales/crowdin/js-vi.yml b/modules/storages/config/locales/crowdin/js-vi.yml index 3f0f6219c91..f503a82e654 100644 --- a/modules/storages/config/locales/crowdin/js-vi.yml +++ b/modules/storages/config/locales/crowdin/js-vi.yml @@ -2,89 +2,89 @@ vi: js: storages: - authentication_error: "Authentication with %{storageType} failed" - link_files_in_storage: "Liên kết tệp trong %{storageType}" - link_existing_files: "Liên kết các tệp hiện có" - upload_files: "Tải lên tệp" - drop_files: "Kéo thả tệp vào đây để tải lên %{name}." - drop_or_click_files: "Kéo thả tệp vào đây hoặc nhấp để tải lên %{name}." - login: "Đăng nhập vào %{storageType}" + authentication_error: "Xác thực bằng %{storageType} không thành công" + link_files_in_storage: "Liên kết các tập tin trong %{storageType}" + link_existing_files: "Liên kết các tập tin hiện có" + upload_files: "Tải tập tin lên" + drop_files: "Thả tệp vào đây để tải chúng lên %{name}." + drop_or_click_files: "Thả tệp vào đây hoặc nhấp để tải chúng lên %{name}." + login: "%{storageType} đăng nhập" login_to: "Đăng nhập vào %{storageType}" no_connection: "Không có kết nối %{storageType}" open_storage: "Mở %{storageType}" select_location: "Chọn vị trí" choose_location: "Chọn vị trí" - new_folder: "New folder" + new_folder: "Thư mục mới" types: nextcloud: "Nextcloud" one_drive: "OneDrive" sharepoint: "SharePoint" default: "Lưu trữ" information: - authentication_error: "The request to %{storageType} could not be authenticated, that's an error." + authentication_error: "Không thể xác thực yêu cầu tới %{storageType}, đó là lỗi." connection_error: > Một số cài đặt %{storageType} không hoạt động. Vui lòng liên hệ với quản trị viên %{storageType} của bạn. - live_data_error: "Lỗi khi lấy thông tin tệp" + live_data_error: "Lỗi khi tìm nạp chi tiết tệp" live_data_error_description: > - Một số dữ liệu %{storageType} không thể được lấy. Vui lòng thử tải lại trang này hoặc liên hệ với quản trị viên %{storageType} của bạn. - no_file_links: "Để liên kết các tệp với gói công việc này, vui lòng thực hiện qua %{storageType}." + Không thể tìm nạp một số dữ liệu %{storageType}. Vui lòng thử tải lại trang này hoặc liên hệ với quản trị viên %{storageType} của bạn. + no_file_links: "Để liên kết các tập tin với gói công việc này, vui lòng thực hiện thông qua %{storageType}." not_logged_in: > Để thêm liên kết, xem hoặc tải lên các tệp liên quan đến gói công việc này, vui lòng đăng nhập vào %{storageType}. - suggest_logout: You can try whether logging out and back in fixes this problem. - suggest_relink: You can try whether re-linking your account via the login button below fixes this problem. + suggest_logout: Bạn có thể thử xem việc đăng xuất và đăng nhập lại có khắc phục được sự cố này hay không. + suggest_relink: Bạn có thể thử xem việc liên kết lại tài khoản của mình thông qua nút đăng nhập bên dưới có khắc phục được sự cố này hay không. files: - already_existing_header: "Tệp này đã tồn tại" + already_existing_header: "Tập tin này đã tồn tại" already_existing_body: > - Một tệp với tên "%{fileName}" đã tồn tại ở vị trí bạn đang cố gắng tải tệp này lên. Bạn muốn làm gì? + Một tệp có tên "%{fileName}" đã tồn tại ở vị trí bạn đang cố tải tệp này lên. Bạn muốn làm gì? directory_not_writeable: "Bạn không có quyền thêm tệp vào thư mục này." - dragging_many_files: "Tải lên %{storageType} chỉ hỗ trợ một tệp mỗi lần." - dragging_folder: "Tải lên %{storageType} không hỗ trợ thư mục." + dragging_many_files: "Việc tải lên %{storageType} chỉ hỗ trợ một tệp cùng một lúc." + dragging_folder: "Việc tải lên %{storageType} không hỗ trợ các thư mục." empty_folder: "Thư mục này trống." - empty_folder_location_hint: "Nhấp vào nút dưới đây để tải tệp lên vị trí này." - file_not_selectable_location: "Việc chọn tệp không khả thi trong quá trình chọn vị trí." + empty_folder_location_hint: "Nhấp vào nút bên dưới để tải tập tin lên vị trí này." + file_not_selectable_location: "Không thể chọn tệp trong quá trình chọn vị trí." project_folder_no_access: > - Bạn không có quyền truy cập vào thư mục dự án. Vui lòng liên hệ với quản trị viên của bạn để được cấp quyền hoặc tải tệp lên vị trí khác. + Bạn không có quyền truy cập vào thư mục dự án. Vui lòng liên hệ với quản trị viên của bạn để có quyền truy cập hoặc tải tệp lên ở một vị trí khác. managed_project_folder_not_available: > - Thư mục dự án được quản lý tự động chưa được tìm thấy. Vui lòng đợi một chút, tải lại trang để lấy dữ liệu mới nhất, và thử lại. + Chưa tìm thấy thư mục dự án được quản lý tự động. Vui lòng đợi một chút, tải lại trang để tìm nạp dữ liệu mới nhất và thử lại. managed_project_folder_no_access: > - Bạn chưa có quyền truy cập vào thư mục dự án được quản lý. Vui lòng đợi một chút và thử lại. - cannot_create_folder: Failed to create folder. Avoid using special characters and symbols and make sure the folder does not exist already. + Bạn chưa có quyền truy cập vào thư mục dự án được quản lý. Vui lòng chờ một chút và thử lại. + cannot_create_folder: Không tạo được thư mục. Tránh sử dụng các ký tự và ký hiệu đặc biệt và đảm bảo thư mục đó chưa tồn tại. upload_keep_both: "Giữ cả hai" upload_replace: "Thay thế" file_links: empty: > - Hiện tại không có tệp liên kết nào với gói công việc này. Bắt đầu liên kết các tệp với hành động bên dưới hoặc từ %{storageType}. + Hiện tại không có tệp nào được liên kết với gói công việc này. Bắt đầu liên kết các tệp bằng hành động bên dưới hoặc từ bên trong %{storageType}. download: "Tải xuống %{fileName}" - open: "Mở tệp trên lưu trữ" - open_location: "Mở tệp tại vị trí" - remove: "Xóa liên kết tệp" + open: "Mở tập tin trên bộ lưu trữ" + open_location: "Mở tệp ở vị trí" + remove: "Xóa liên kết tập tin" remove_confirmation: > - Bạn có chắc chắn muốn gỡ liên kết tệp khỏi gói công việc này không? Việc gỡ liên kết không ảnh hưởng đến tệp gốc và chỉ loại bỏ kết nối với gói công việc này. + Bạn có chắc chắn muốn hủy liên kết tệp khỏi gói công việc này không? Việc hủy liên kết không ảnh hưởng đến tệp gốc và chỉ xóa kết nối với gói công việc này. remove_short: "Xóa liên kết" - select: "Chọn tệp" + select: "Chọn tập tin" select_all: "Chọn tất cả" selection: - zero: "Chọn các tệp để liên kết" - other: "Liên kết %{count} tệp" + zero: "Chọn tập tin để liên kết" + other: "Liên kết tập tin %{count}" success_create: - other: "Đã tạo thành công %{count} liên kết tệp." + other: "Đã tạo thành công liên kết tệp %{count}." upload_error: default: > - Tệp của bạn (%{fileName}) không thể được tải lên. + Không thể tải lên tệp của bạn (%{fileName}). 403: > - Tệp của bạn (%{fileName}) không thể được tải lên do hạn chế của hệ thống. Vui lòng liên hệ với quản trị viên của bạn để biết thêm thông tin. + Không thể tải lên tệp của bạn (%{fileName}) do hạn chế của hệ thống. Vui lòng liên hệ với quản trị viên của bạn để biết thêm thông tin. 413: > - Tệp của bạn (%{fileName}) lớn hơn mức tối đa mà OpenProject có thể tải lên đến %{storageType}. Bạn có thể tải lên trực tiếp vào %{storageType} trước và sau đó liên kết tệp. + Tệp của bạn (%{fileName}) lớn hơn tệp mà OpenProject có thể tải lên %{storageType}. Bạn có thể tải trực tiếp lên %{storageType} trước rồi liên kết tệp. 507: > - Tệp của bạn (%{fileName}) lớn hơn hạn ngạch lưu trữ cho phép. Liên hệ với quản trị viên của bạn để thay đổi hạn ngạch này. + Tệp của bạn (%{fileName}) lớn hơn hạn mức bộ nhớ cho phép. Hãy liên hệ với quản trị viên của bạn để sửa đổi hạn ngạch này. detail: nextcloud: > - Vui lòng kiểm tra rằng phiên bản mới nhất của ứng dụng Nextcloud "Tích hợp OpenProject" đã được cài đặt và liên hệ với quản trị viên của bạn để biết thêm thông tin. + Vui lòng kiểm tra xem phiên bản mới nhất của Ứng dụng Nextcloud "Tích hợp OpenProject" đã được cài đặt chưa và liên hệ với quản trị viên của bạn để biết thêm thông tin. link_uploaded_file_error: > - Đã xảy ra lỗi khi liên kết tệp vừa tải lên '%{fileName}' với gói công việc %{workPackageId}. + Đã xảy ra lỗi khi liên kết tệp được tải lên gần đây '%{fileName}' với gói công việc %{workPackageId}. tooltip: - not_logged_in: "Vui lòng đăng nhập vào lưu trữ để truy cập tệp này." - view_not_allowed: "Bạn không có quyền xem tệp này." - not_found: "Không thể tìm thấy tệp này." + not_logged_in: "Vui lòng đăng nhập vào bộ lưu trữ để truy cập tập tin này." + view_not_allowed: "Bạn không có quyền xem tập tin này." + not_found: "Không thể tìm thấy tập tin này." already_linked_file: "Tệp này đã được liên kết với gói công việc này." already_linked_directory: "Thư mục này đã được liên kết với gói công việc này." diff --git a/modules/storages/config/locales/crowdin/ko.yml b/modules/storages/config/locales/crowdin/ko.yml index 323e327964f..3f79855017b 100644 --- a/modules/storages/config/locales/crowdin/ko.yml +++ b/modules/storages/config/locales/crowdin/ko.yml @@ -104,20 +104,20 @@ ko: create_folder: '관리되는 프로젝트 폴더 생성:' ensure_root_folder_permissions: '기본 폴더 권한 설정:' hide_inactive_folders: '비활성 폴더 숨기기 단계:' - remote_folders: 'Read contents of the team folder:' + remote_folders: '팀 폴더의 콘텐츠 읽기:' remove_user_from_group: '그룹에서 사용자 제거:' rename_project_folder: '관리되는 프로젝트 폴더의 이름 바꾸기:' one_drive_sync_service: create_folder: '관리되는 프로젝트 폴더 생성:' ensure_root_folder_permissions: '기본 폴더 권한 설정:' hide_inactive_folders: '비활성 폴더 숨기기 단계:' - remote_folders: 'Read contents of the drive root folder:' + remote_folders: '드라이브 루트 폴더의 콘텐츠 읽기:' rename_project_folder: '관리되는 프로젝트 폴더의 이름 바꾸기:' sharepoint_sync_service: create_folder: '관리되는 프로젝트 폴더 생성:' ensure_root_folder_permissions: '기본 폴더 권한 설정:' hide_inactive_folders: '비활성 폴더 숨기기 단계:' - remote_folders: 'Read contents of the drive root folder:' + remote_folders: '드라이브 루트 폴더의 콘텐츠 읽기:' rename_project_folder: '관리되는 프로젝트 폴더의 이름 바꾸기:' errors: messages: @@ -140,7 +140,7 @@ ko: conflict: '%{folder_name} 폴더가 이미 %{parent_location}에 있습니다.' not_found: "%{parent_location}을(를) 찾을 수 없습니다." ensure_root_folder_permissions: - not_found: "%{group_folder} wasn't found. Please check your Nextcloud Team Folder setup." + not_found: "%{group_folder}을(를) 찾을 수 없습니다. Nextcloud Team Folder 설정을 확인하세요." permission_not_set: '- %{group_folder}에 대한 권한을 설정할 수 없습니다.' hide_inactive_folders: permission_not_set: '- %{path}에 대한 권한을 설정할 수 없습니다.' @@ -230,7 +230,7 @@ ko: storage_delete_result_3: 자동으로 관리되는 프로젝트 폴더 및 이 폴더 내 모든 파일이 삭제됩니다 dependencies: nextcloud: - group_folders_app: Team Folders + group_folders_app: 팀 폴더 integration_app: Integration OpenProject enabled_in_projects: setup_incomplete_description: 이 저장소의 설정이 완료되지 않았습니다. 여러 프로젝트에서 활성화하기 전에 설정을 완료하세요. @@ -277,11 +277,11 @@ ko: client_folder_creation: 자동 폴더 생성 client_folder_removal: 자동 폴더 삭제 drive_contents: 드라이브 콘텐츠 - files_request: Fetching team folder files + files_request: 팀 폴더 파일 가져오기 header: 자동으로 관리되는 프로젝트 폴더 - team_folder_app: 'Dependency: Team Folders' - team_folder_contents: Team folder content - team_folder_presence: Team folder exists + team_folder_app: '종속성: 팀 폴더' + team_folder_contents: 팀 폴더 콘텐츠 + team_folder_presence: 팀 폴더가 존재합니다 userless_access: 서버 측 요청 인증 authentication: existing_token: 사용자 토큰 @@ -320,8 +320,8 @@ ko: nc_oauth_request_not_found: 현재 연결된 사용자를 가져올 엔드포인트를 찾을 수 없습니다. 자세한 내용은 서버 로그를 확인하세요. nc_oauth_request_unauthorized: 현재 사용자는 원격 파일 저장소에 액세스할 수 있는 권한이 없습니다. 자세한 내용은 서버 로그를 확인하세요. nc_oauth_token_missing: 사용자가 아직 Nextcloud 계정을 링크하지 않았기 때문에 OpenProject가 Nextcloud와의 사용자 수준 통신을 테스트할 수 없습니다. - nc_team_folder_not_found: The team folder could not be found. - nc_unexpected_content: Unexpected content found in the managed team folder. + nc_team_folder_not_found: 팀 폴더를 찾을 수 없습니다. + nc_unexpected_content: 관리되는 팀 폴더에서 예기치 않은 콘텐츠가 발견되었습니다. nc_userless_access_denied: 구성된 앱 암호가 잘못되었습니다. not_configured: 연결에 대한 유효성 검사를 할 수 없습니다. 먼저 구성을 완료하세요. od_client_cant_delete_folder: 클라이언트에서 폴더를 삭제하는 중에 문제가 발생했습니다. 저장소에 대한 설정 설명서를 확인하세요. diff --git a/modules/storages/config/locales/crowdin/pl.yml b/modules/storages/config/locales/crowdin/pl.yml index 4e384d269b3..340b15a72d4 100644 --- a/modules/storages/config/locales/crowdin/pl.yml +++ b/modules/storages/config/locales/crowdin/pl.yml @@ -104,20 +104,20 @@ pl: create_folder: 'Utworzenie zarządzanego folderu projektu:' ensure_root_folder_permissions: 'Ustaw podstawowe uprawnienia do folderu:' hide_inactive_folders: 'Krok „Ukryj nieaktywne foldery”:' - remote_folders: 'Read contents of the team folder:' + remote_folders: 'Odczytaj zawartość folderu zespołu:' remove_user_from_group: 'Usuń użytkownika z grupy:' rename_project_folder: 'Zmień nazwę zarządzanego folderu projektu:' one_drive_sync_service: create_folder: 'Utworzenie zarządzanego folderu projektu:' ensure_root_folder_permissions: 'Ustaw podstawowe uprawnienia do folderu:' hide_inactive_folders: 'Krok „Ukryj nieaktywne foldery”:' - remote_folders: 'Read contents of the drive root folder:' + remote_folders: 'Odczytaj zawartość folderu głównego dysku:' rename_project_folder: 'Zmień nazwę zarządzanego folderu projektu:' sharepoint_sync_service: create_folder: 'Utworzenie zarządzanego folderu projektu:' ensure_root_folder_permissions: 'Ustaw podstawowe uprawnienia do folderu:' hide_inactive_folders: 'Krok „Ukryj nieaktywne foldery”:' - remote_folders: 'Read contents of the drive root folder:' + remote_folders: 'Odczytaj zawartość folderu głównego dysku:' rename_project_folder: 'Zmień nazwę zarządzanego folderu projektu:' errors: messages: @@ -140,7 +140,7 @@ pl: conflict: Folder %{folder_name} już istnieje w lokalizacji %{parent_location}. not_found: "Nie znaleziono lokalizacji %{parent_location}." ensure_root_folder_permissions: - not_found: "%{group_folder} wasn't found. Please check your Nextcloud Team Folder setup." + not_found: "Nie znaleziono folderu %{group_folder}. Sprawdź konfigurację folderu zespołu Nextcloud." permission_not_set: nie można ustawić uprawnień do folderu %{group_folder}. hide_inactive_folders: permission_not_set: nie można ustawić uprawnień do lokalizacji %{path}. @@ -230,7 +230,7 @@ pl: storage_delete_result_3: Automatycznie zarządzany folder projektu i wszystkie znajdujące się w nim pliki zostaną usunięte dependencies: nextcloud: - group_folders_app: Team Folders + group_folders_app: Foldery zespołu integration_app: Integration OpenProject enabled_in_projects: setup_incomplete_description: Ten magazyn ma niekompletną konfigurację. Ukończ konfigurację przed włączeniem go w wielu projektach. @@ -277,11 +277,11 @@ pl: client_folder_creation: Automatyczne tworzenie folderów client_folder_removal: Automatyczne usuwanie folderów drive_contents: Zawartość dysku - files_request: Fetching team folder files + files_request: Pobieranie plików folderu zespołu header: Automatycznie zarządzane foldery projektu - team_folder_app: 'Dependency: Team Folders' - team_folder_contents: Team folder content - team_folder_presence: Team folder exists + team_folder_app: 'Zależność: foldery zespołów' + team_folder_contents: Zawartość folderu zespołu + team_folder_presence: Folder zespołu istnieje userless_access: Uwierzytelnianie żądań po stronie serwera authentication: existing_token: Token użytkownika @@ -326,8 +326,8 @@ pl: nc_oauth_request_not_found: Nie znaleziono punktu końcowego, z którego można pobrać informacje o aktualnie połączonym użytkowniku. Aby uzyskać więcej informacji, sprawdź dzienniki serwera. nc_oauth_request_unauthorized: Bieżący użytkownik nie ma uprawnień dostępu do zdalnego magazynu plików. Aby uzyskać więcej informacji, sprawdź dzienniki serwera. nc_oauth_token_missing: Aplikacja OpenProject nie może przetestować komunikacji z usługą Nextcloud na poziomie użytkownika, ponieważ użytkownik nie powiązał jeszcze swojego konta Nextcloud. - nc_team_folder_not_found: The team folder could not be found. - nc_unexpected_content: Unexpected content found in the managed team folder. + nc_team_folder_not_found: Nie można znaleźć folderu zespołu. + nc_unexpected_content: W folderze zarządzanego zespołu znaleziono nieoczekiwaną zawartość. nc_userless_access_denied: Skonfigurowane hasło aplikacji jest nieprawidłowe. not_configured: Nie można zweryfikować połączenia. Najpierw zakończ konfigurację. od_client_cant_delete_folder: Klient ma problemy z usunięciem folderów. Sprawdź dokumentację konfiguracji swojej pamięci masowej. diff --git a/modules/storages/config/locales/crowdin/uk.yml b/modules/storages/config/locales/crowdin/uk.yml index 81e6fb1720c..9a8cd28b187 100644 --- a/modules/storages/config/locales/crowdin/uk.yml +++ b/modules/storages/config/locales/crowdin/uk.yml @@ -104,7 +104,7 @@ uk: create_folder: 'Створення керованої папки проєкту' ensure_root_folder_permissions: 'Установіть дозволи для основної папки:' hide_inactive_folders: 'Етап «Приховайте неактивні папки»:' - remote_folders: 'Читання вмісту папки групи:' + remote_folders: 'Читання вмісту папки команди:' remove_user_from_group: 'Вилучення користувача з групи:' rename_project_folder: 'Перейменування керованої папки проєкту:' one_drive_sync_service: diff --git a/modules/storages/config/locales/crowdin/vi.yml b/modules/storages/config/locales/crowdin/vi.yml index 305b6a34a52..215249d05c8 100644 --- a/modules/storages/config/locales/crowdin/vi.yml +++ b/modules/storages/config/locales/crowdin/vi.yml @@ -2,43 +2,43 @@ vi: activerecord: attributes: oauth_client: - client_id: ID người dùng - client_secret: Khóa bí mật người dùng + client_id: ID khách hàng + client_secret: Bí mật của khách hàng project: - project_folder_type: Project folder type + project_folder_type: Loại thư mục dự án storages/file_link: - origin: Origin + origin: Xuất xứ storage: Lưu trữ storages/nextcloud_storage: authentication_methods: - oauth2_sso: Single-Sign-On through OpenID Connect Identity Provider - oauth2_sso_with_two_way_oauth2_fallback: Single-Sign-On with Two-Way OAuth 2.0 as fallback - two_way_oauth2: Two-way OAuth 2.0 authorization code flow - storage_audience: Storage Audience - token_exchange_scope: Storage Scope + oauth2_sso: Đăng nhập một lần thông qua Nhà cung cấp nhận dạng OpenID Connect + oauth2_sso_with_two_way_oauth2_fallback: Đăng nhập một lần với OAuth 2.0 hai chiều làm dự phòng + two_way_oauth2: Luồng mã ủy quyền OAuth 2.0 hai chiều + storage_audience: Đối tượng lưu trữ + token_exchange_scope: Phạm vi lưu trữ storages/project_storage: project_folder: Thư mục dự án - project_folder_mode: Project folder mode + project_folder_mode: Chế độ thư mục dự án storage: Lưu trữ - storage_url: Storage URL + storage_url: URL lưu trữ storages/sharepoint_storage: - host: Sharepoint Site URL - library: Library ID - name: Tên - site: Site ID + host: URL trang web Sharepoint + library: Mã thư viện + name: tên + site: ID trang web storages/storage: - authentication_method: Authentication Method - creator: Người tạo - drive: ID Ổ đĩa - host: Máy chủ - name: Tên + authentication_method: Phương thức xác thực + creator: Người sáng tạo + drive: ID ổ đĩa + host: chủ nhà + name: tên password: Mật khẩu ứng dụng provider_type: Loại nhà cung cấp - tenant: ID Thư mục (người thuê) + tenant: ID thư mục (đối tượng thuê) errors: messages: invalid_host_url: không phải là một URL hợp lệ. - invalid_sharepoint_url: is not a valid SharePoint site, library, or document URL. + invalid_sharepoint_url: không phải là URL tài liệu, thư viện hoặc trang web SharePoint hợp lệ. not_linked_to_project: không được liên kết với dự án. models: storages/file_link: @@ -50,46 +50,46 @@ vi: project_folder_id: blank: Vui lòng chọn một thư mục. project_folder_mode: - mode_unavailable: không khả dụng cho lưu trữ này. + mode_unavailable: không có sẵn cho bộ lưu trữ này. project_ids: blank: Vui lòng chọn một dự án. storages/storage: attributes: host: - authorization_header_missing: không được cấu hình đầy đủ. Phiên bản Nextcloud không nhận được tiêu đề "Authorization", điều này cần thiết cho xác thực Bearer token của các yêu cầu API. Vui lòng kiểm tra lại cấu hình máy chủ HTTP của bạn. - cannot_be_connected_to: không thể truy cập được. Vui lòng đảm bảo máy chủ có thể truy cập được và ứng dụng tích hợp OpenProject đã được cài đặt. + authorization_header_missing: chưa được thiết lập đầy đủ. Phiên bản Nextcloud không nhận được tiêu đề "Ủy quyền", tiêu đề này cần thiết cho việc ủy ​​quyền các yêu cầu API dựa trên mã thông báo Bearer. Vui lòng kiểm tra kỹ cấu hình máy chủ HTTP của bạn. + cannot_be_connected_to: không thể đạt được. Hãy đảm bảo rằng máy chủ có thể truy cập được và ứng dụng tích hợp OpenProject đã được cài đặt. minimal_nextcloud_version_unmet: không đáp ứng yêu cầu phiên bản tối thiểu (phải là Nextcloud 23 trở lên) not_nextcloud_server: không phải là máy chủ Nextcloud - op_application_not_installed: có vẻ như chưa cài đặt ứng dụng "Tích hợp OpenProject". Vui lòng cài đặt ứng dụng này trước và sau đó thử lại. + op_application_not_installed: dường như chưa cài đặt ứng dụng "Tích hợp OpenProject". Vui lòng cài đặt nó trước và sau đó thử lại. password: invalid_password: không hợp lệ. - unknown_error: could not be validated with the file storage provider. Please verify that the connection is functioning properly. + unknown_error: không thể xác thực với nhà cung cấp lưu trữ tệp. Vui lòng xác minh rằng kết nối đang hoạt động bình thường. models: - file_link: Tệp + file_link: tập tin storages/storage: Lưu trữ api_v3: errors: - too_many_elements_created_at_once: Tạo quá nhiều phần tử cùng một lúc. Mong đợi tối đa %{max}, nhưng nhận được %{actual}. - external_file_storages: Lưu trữ tệp bên ngoài - permission_create_files: 'Thư mục dự án được quản lý tự động: Tạo tệp' - permission_create_files_explanation: Quyền này chỉ có sẵn cho các lưu trữ Nextcloud + too_many_elements_created_at_once: Quá nhiều yếu tố được tạo cùng một lúc. Dự kiến ​​nhiều nhất là %{max}, đã nhận được %{actual}. + external_file_storages: Kho lưu trữ tập tin bên ngoài + permission_create_files: 'Thư mục dự án được quản lý tự động: Tạo tập tin' + permission_create_files_explanation: Quyền này chỉ khả dụng cho kho lưu trữ Nextcloud permission_delete_files: 'Thư mục dự án được quản lý tự động: Xóa tệp' - permission_delete_files_explanation: Quyền này chỉ có sẵn cho các lưu trữ Nextcloud + permission_delete_files_explanation: Quyền này chỉ khả dụng cho kho lưu trữ Nextcloud permission_header_for_project_module_storages: Các thư mục dự án được quản lý tự động - permission_manage_file_links: Quản lý liên kết tệp - permission_manage_files_in_project: Quản lý tệp trong dự án + permission_manage_file_links: Quản lý liên kết tập tin + permission_manage_files_in_project: Quản lý tập tin trong dự án permission_read_files: 'Thư mục dự án được quản lý tự động: Đọc tệp' permission_share_files: 'Thư mục dự án được quản lý tự động: Chia sẻ tệp' - permission_share_files_explanation: Quyền này chỉ có sẵn cho các lưu trữ Nextcloud - permission_view_file_links: Xem liên kết tệp + permission_share_files_explanation: Quyền này chỉ khả dụng cho kho lưu trữ Nextcloud + permission_view_file_links: Xem liên kết tập tin permission_write_files: 'Thư mục dự án được quản lý tự động: Ghi tệp' - project_module_storages: Tập tin + project_module_storages: tập tin project_storages: edit_project_folder: - label: Sửa thư mục dự án + label: Chỉnh sửa thư mục dự án open: - contact_admin: Please contact your administrator to resolve this error. - remote_identity_error: An unexpected error occurred while connecting to the storage. + contact_admin: Vui lòng liên hệ với quản trị viên của bạn để giải quyết lỗi này. + remote_identity_error: Đã xảy ra lỗi không mong muốn khi kết nối với bộ lưu trữ. project_folder_mode: automatic: Được quản lý tự động inactive: Không có thư mục cụ thể @@ -102,33 +102,33 @@ vi: nextcloud_sync_service: add_user_to_group: 'Thêm người dùng vào nhóm:' create_folder: 'Tạo thư mục dự án được quản lý:' - ensure_root_folder_permissions: 'Thiết lập quyền cho thư mục cơ sở:' - hide_inactive_folders: 'Ẩn bước các thư mục không hoạt động:' - remote_folders: 'Read contents of the team folder:' + ensure_root_folder_permissions: 'Đặt quyền thư mục cơ sở:' + hide_inactive_folders: 'Ẩn các thư mục không hoạt động Bước:' + remote_folders: 'Đọc nội dung của thư mục nhóm:' remove_user_from_group: 'Xóa người dùng khỏi nhóm:' rename_project_folder: 'Đổi tên thư mục dự án được quản lý:' one_drive_sync_service: create_folder: 'Tạo thư mục dự án được quản lý:' - ensure_root_folder_permissions: 'Thiết lập quyền cho thư mục cơ sở:' - hide_inactive_folders: 'Ẩn bước các thư mục không hoạt động:' - remote_folders: 'Read contents of the drive root folder:' + ensure_root_folder_permissions: 'Đặt quyền thư mục cơ sở:' + hide_inactive_folders: 'Ẩn các thư mục không hoạt động Bước:' + remote_folders: 'Đọc nội dung của thư mục gốc trên ổ đĩa:' rename_project_folder: 'Đổi tên thư mục dự án được quản lý:' sharepoint_sync_service: create_folder: 'Tạo thư mục dự án được quản lý:' - ensure_root_folder_permissions: 'Thiết lập quyền cho thư mục cơ sở:' - hide_inactive_folders: 'Ẩn bước các thư mục không hoạt động:' - remote_folders: 'Read contents of the drive root folder:' + ensure_root_folder_permissions: 'Đặt quyền thư mục cơ sở:' + hide_inactive_folders: 'Ẩn các thư mục không hoạt động Bước:' + remote_folders: 'Đọc nội dung của thư mục gốc trên ổ đĩa:' rename_project_folder: 'Đổi tên thư mục dự án được quản lý:' errors: messages: error: Đã xảy ra lỗi không mong muốn. Vui lòng kiểm tra nhật ký OpenProject để biết thêm thông tin hoặc liên hệ với quản trị viên - forbidden: OpenProject could not access the requested resource. Please check your permissions configuration on the Storage Provider. - unauthorized: OpenProject could not authenticate with the Storage Provider. Please ensure that you have access to it. + forbidden: OpenProject không thể truy cập tài nguyên được yêu cầu. Vui lòng kiểm tra cấu hình quyền của bạn trên Nhà cung cấp bộ nhớ. + unauthorized: OpenProject không thể xác thực với Nhà cung cấp lưu trữ. Hãy đảm bảo rằng bạn có quyền truy cập vào nó. models: copy_project_folders_service: - conflict: Thư mục %{destination_path} đã tồn tại. Đang tạm dừng quá trình để tránh ghi đè. + conflict: Thư mục %{destination_path} đã tồn tại. Làm gián đoạn quá trình để tránh ghi đè. error: Đã xảy ra lỗi không mong muốn. Vui lòng kiểm tra nhật ký OpenProject để biết thêm thông tin hoặc liên hệ với quản trị viên - forbidden: OpenProject không thể truy cập thư mục nguồn. Vui lòng kiểm tra cấu hình quyền của bạn trên Nhà cung cấp lưu trữ + forbidden: OpenProject không thể truy cập thư mục nguồn. Vui lòng kiểm tra cấu hình quyền của bạn trên Nhà cung cấp bộ nhớ not_found: Không tìm thấy vị trí mẫu nguồn %{source_path}. unauthorized: OpenProject không thể xác thực với Nhà cung cấp lưu trữ. Vui lòng kiểm tra cấu hình lưu trữ của bạn nextcloud_sync_service: @@ -137,423 +137,424 @@ vi: conflict: 'Không thể thêm người dùng %{user} vào nhóm %{group} vì lý do sau: %{reason}' failed_to_add: 'Không thể thêm người dùng %{user} vào nhóm %{group} vì lý do sau: %{reason}' create_folder: - conflict: The folder %{folder_name} already exists on %{parent_location}. - not_found: "Không tìm thấy %{parent_location}." + conflict: Thư mục %{folder_name} đã tồn tại trên %{parent_location}. + not_found: "%{parent_location} không được tìm thấy." ensure_root_folder_permissions: - not_found: "%{group_folder} wasn't found. Please check your Nextcloud Team Folder setup." - permission_not_set: không thể thiết lập quyền trên %{group_folder}. + not_found: "%{group_folder} Không tìm thấy. Vui lòng kiểm tra cài đặt thư mục nhóm Nextcloud của bạn." + permission_not_set: không thể đặt quyền trên %{group_folder}. hide_inactive_folders: - permission_not_set: không thể thiết lập quyền trên %{path}. + permission_not_set: không thể đặt quyền trên %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. - not_found: "Không tìm thấy %{group_folder} thư mục. Hãy kiểm tra cấu hình Nextcloud." + not_allowed: '%{username} không có quyền truy cập vào thư mục %{group_folder}. Vui lòng kiểm tra quyền của thư mục trên Nextcloud.' + not_found: "Không tìm thấy thư mục %{group_folder}. Vui lòng kiểm tra thiết lập Nextcloud của bạn." remove_user_from_group: conflict: 'Không thể xóa người dùng %{user} khỏi nhóm %{group} vì lý do sau: %{reason}' failed_to_remove: 'Không thể xóa người dùng %{user} khỏi nhóm %{group} vì lý do sau: %{reason}' rename_project_folder: - conflict: OpenProject could not rename the project folder to %{current_path} as a folder with the same name already exists + conflict: OpenProject không thể đổi tên thư mục dự án thành %{current_path} vì thư mục có cùng tên đã tồn tại forbidden: Người dùng OpenProject không có quyền truy cập vào thư mục %{current_path}. - not_found: "Không tìm thấy %{current_path}." + not_found: "%{current_path} không được tìm thấy." set_folders_permissions: - permission_not_set: không thể thiết lập quyền trên %{path}. - error: An unexpected error occurred. Please ensure that your Nextcloud instance is reachable and check OpenProject worker logs for more information + permission_not_set: không thể đặt quyền trên %{path}. + error: Đã xảy ra lỗi không mong muốn. Vui lòng đảm bảo rằng phiên bản Nextcloud của bạn có thể truy cập được và kiểm tra nhật ký nhân viên OpenProject để biết thêm thông tin group_does_not_exist: "%{group} không tồn tại. Kiểm tra cấu hình phiên bản Nextcloud của bạn." - insufficient_privileges: OpenProject không có đủ quyền để thêm %{user} vào %{group}. Kiểm tra cài đặt nhóm của bạn trong Nextcloud. + insufficient_privileges: OpenProject không có đủ đặc quyền để thêm %{user} vào %{group}. Kiểm tra cài đặt nhóm của bạn trong Nextcloud. not_allowed: Nextcloud chặn yêu cầu. - not_found: OpenProject could not find the file on the Nextcloud Storage Provider. Please check if it wasn't deleted. - unauthorized: OpenProject không thể đồng bộ với Nextcloud. Hãy kiểm tra cấu hình lưu trữ và Nextcloud. + not_found: OpenProject không thể tìm thấy tệp trên Nhà cung cấp lưu trữ Nextcloud. Vui lòng kiểm tra xem nó có bị xóa không. + unauthorized: OpenProject không thể đồng bộ hóa với Nextcloud. Vui lòng kiểm tra bộ nhớ và cấu hình Nextcloud của bạn. user_does_not_exist: "%{user} không tồn tại trong Nextcloud." one_drive_sync_service: attributes: create_folder: - conflict: '%{folder_name} đã tồn tại ở %{parent_location}.' - not_found: "Không tìm thấy %{parent_location}." + conflict: '%{folder_name} đã tồn tại trên %{parent_location}.' + not_found: "%{parent_location} không được tìm thấy." hide_inactive_folders: - permission_not_set: không thể thiết lập quyền trên %{path}. + permission_not_set: không thể đặt quyền trên %{path}. remote_folders: - request_error: OpenProject không thể truy cập ổ đĩa %{drive_id} của bạn. Vui lòng kiểm tra xem cấu hình lưu trữ của bạn có đúng không. + request_error: OpenProject không thể truy cập ổ đĩa của bạn %{drive_id}. Vui lòng kiểm tra xem cấu hình lưu trữ của bạn có đúng không. rename_project_folder: - conflict: OpenProject could not rename the folder %{current_path} to %{project_folder_name} as a folder with the same name already exists. - forbidden: OpenProject không có quyền truy cập vào %{current_path} để đổi tên nó. - not_found: "Không tìm thấy %{current_path}." + conflict: OpenProject không thể đổi tên thư mục %{current_path} thành %{project_folder_name} vì thư mục có cùng tên đã tồn tại. + forbidden: OpenProject không có quyền truy cập %{current_path} để đổi tên nó. + not_found: "%{current_path} không được tìm thấy." set_folders_permissions: - permission_not_set: không thể thiết lập quyền trên %{path}. - error: An unexpected error occurred. Please ensure that OneDrive is reachable and check OpenProject worker logs for more information. - not_allowed: OpenProject không được phép truy cập ổ đĩa OneDrive của bạn. Vui lòng kiểm tra các quyền được thiết lập trên Ứng dụng Azure. - unauthorized: OpenProject không thể đồng bộ với OneDrive. Hãy kiểm tra cấu hình lưu trữ và ứng dụng Azure.. + permission_not_set: không thể đặt quyền trên %{path}. + error: Đã xảy ra lỗi không mong muốn. Vui lòng đảm bảo rằng OneDrive có thể truy cập được và kiểm tra nhật ký nhân viên OpenProject để biết thêm thông tin. + not_allowed: OpenProject không được phép truy cập vào ổ OneDrive của bạn. Vui lòng kiểm tra các quyền được đặt trên Ứng dụng Azure. + unauthorized: OpenProject không thể đồng bộ hóa với OneDrive. Vui lòng kiểm tra bộ nhớ và cấu hình Ứng dụng Azure của bạn. user_does_not_exist: "%{user} không tồn tại trong Nextcloud." sharepoint_sync_service: attributes: create_folder: - conflict: '%{folder_name} đã tồn tại ở %{parent_location}.' - not_found: "Không tìm thấy %{parent_location}." + conflict: '%{folder_name} đã tồn tại trên %{parent_location}.' + not_found: "%{parent_location} không được tìm thấy." hide_inactive_folders: - permission_not_set: không thể thiết lập quyền trên %{path}. + permission_not_set: không thể đặt quyền trên %{path}. rename_project_folder: - conflict: OpenProject không thể đổi tên thư mục %{current_path} thành %{project_folder_name} vì đã có một thư mục có cùng tên - forbidden: OpenProject không có quyền truy cập vào %{current_path} để đổi tên nó. - not_found: "Không tìm thấy %{current_path}." + conflict: OpenProject không thể đổi tên thư mục %{current_path} thành %{project_folder_name} vì thư mục có cùng tên đã tồn tại + forbidden: OpenProject không có quyền truy cập %{current_path} để đổi tên nó. + not_found: "%{current_path} không được tìm thấy." set_folders_permissions: - permission_not_set: không thể thiết lập quyền trên %{path}. - error: An unexpected error occurred. Please ensure that OneDrive is reachable and check OpenProject worker logs for more information - not_allowed: OpenProject không được phép truy cập ổ đĩa OneDrive của bạn. Vui lòng kiểm tra các quyền được thiết lập trên Ứng dụng Azure. - not_found: OpenProject could not access your library %{drive_name}. Please check you Sharepoint site document libraries. - unauthorized: OpenProject không thể đồng bộ với OneDrive. Hãy kiểm tra cấu hình lưu trữ và ứng dụng Azure.. + permission_not_set: không thể đặt quyền trên %{path}. + error: Đã xảy ra lỗi không mong muốn. Vui lòng đảm bảo rằng OneDrive có thể truy cập được và kiểm tra nhật ký nhân viên OpenProject để biết thêm thông tin + not_allowed: OpenProject không được phép truy cập vào ổ OneDrive của bạn. Vui lòng kiểm tra các quyền được đặt trên Ứng dụng Azure. + not_found: OpenProject không thể truy cập thư viện của bạn %{drive_name}. Vui lòng kiểm tra thư viện tài liệu trang Sharepoint của bạn. + unauthorized: OpenProject không thể đồng bộ hóa với OneDrive. Vui lòng kiểm tra bộ nhớ và cấu hình Ứng dụng Azure của bạn. user_does_not_exist: "%{user} không tồn tại trong Nextcloud." upload_link_service: - not_found: The destination folder %{folder} could not be found on %{storage_name}. + not_found: Không thể tìm thấy thư mục đích %{folder} trên %{storage_name}. storages: buttons: - done_continue: Hoàn tất, tiếp tục - open_storage: Mở lưu trữ tập tin - replace_oauth_application: Thay thế OAuth OpenProject - replace_oauth_client: Thay thế OAuth %{provider_type} + done_continue: Xong, tiếp tục + open_storage: Mở kho lưu trữ tập tin + replace_oauth_application: Thay thế OpenProject OAuth + replace_oauth_client: Thay thế %{provider_type} OAuth save_and_continue: Lưu và tiếp tục select_folder: Chọn thư mục configuration_checks: oauth_client_incomplete: nextcloud: Cho phép OpenProject truy cập dữ liệu Nextcloud bằng OAuth. - one_drive: Allow OpenProject to access Azure data using OAuth to connect OneDrive. - sharepoint: Allow OpenProject to access SharePoint data using OAuth. + one_drive: Cho phép OpenProject truy cập dữ liệu Azure bằng OAuth để kết nối OneDrive. + sharepoint: Cho phép OpenProject truy cập dữ liệu SharePoint bằng OAuth. redirect_uri_incomplete: - one_drive: Hoàn tất cấu hình với URI chuyển hướng chính xác. - sharepoint: Hoàn tất cấu hình với URI chuyển hướng chính xác. - confirm_replace_oauth_application: Hành động này sẽ đặt lại các thông tin xác thực OAuth hiện tại. Sau khi xác nhận, bạn sẽ phải nhập lại thông tin xác thực tại nhà cung cấp lưu trữ và tất cả người dùng từ xa sẽ phải xác thực lại với OpenProject. Bạn có chắc chắn muốn tiếp tục không? - confirm_replace_oauth_client: Hành động này sẽ đặt lại các thông tin xác thực OAuth hiện tại. Sau khi xác nhận, bạn sẽ phải nhập thông tin mới từ nhà cung cấp lưu trữ và tất cả người dùng sẽ phải xác thực lại với %{provider_type}. Bạn có chắc chắn muốn tiếp tục không? + one_drive: Hoàn tất thiết lập với chuyển hướng URI chính xác. + sharepoint: Hoàn tất thiết lập với chuyển hướng URI chính xác. + confirm_replace_oauth_application: Hành động này sẽ đặt lại thông tin xác thực OAuth hiện tại. Sau khi xác nhận, bạn sẽ phải nhập lại thông tin đăng nhập tại nhà cung cấp dịch vụ lưu trữ và tất cả người dùng từ xa sẽ phải ủy quyền lại cho OpenProject. Bạn có chắc chắn muốn tiếp tục không? + confirm_replace_oauth_client: Hành động này sẽ đặt lại thông tin xác thực OAuth hiện tại. Sau khi xác nhận, bạn sẽ phải nhập thông tin xác thực mới từ nhà cung cấp bộ nhớ và tất cả người dùng sẽ phải ủy quyền lại cho %{provider_type}. Bạn có chắc chắn muốn tiếp tục không? delete_warning: - project_storage: Are you sure you want to remove %{file_storage} from this project? - project_storage_delete_result_1: All links to corresponding files and folders will be removed - project_storage_delete_result_2: The automatically-managed project folder and all files in it will be deleted - storage: Are you sure you want to delete %{file_storage} as an external file storage? - storage_delete_result_1: The storage will be removed from all projects currently using it - storage_delete_result_2: All links to corresponding files and folders will be removed - storage_delete_result_3: The automatically-managed project folder and all files in it will be deleted + project_storage: Bạn có chắc chắn muốn xóa %{file_storage} khỏi dự án này không? + project_storage_delete_result_1: Tất cả các liên kết đến các tập tin và thư mục tương ứng sẽ bị xóa + project_storage_delete_result_2: Thư mục dự án được quản lý tự động và tất cả các tệp trong đó sẽ bị xóa + storage: Bạn có chắc chắn muốn xóa %{file_storage} làm nơi lưu trữ tệp bên ngoài không? + storage_delete_result_1: Bộ nhớ sẽ bị xóa khỏi tất cả các dự án hiện đang sử dụng nó + storage_delete_result_2: Tất cả các liên kết đến các tập tin và thư mục tương ứng sẽ bị xóa + storage_delete_result_3: Thư mục dự án được quản lý tự động và tất cả các tệp trong đó sẽ bị xóa dependencies: nextcloud: - group_folders_app: Team Folders - integration_app: Tích hợp OpenProject + group_folders_app: Thư mục nhóm + integration_app: Tích hợp Dự án mở enabled_in_projects: - setup_incomplete_description: Bộ lưu trữ này có thiết lập chưa hoàn chỉnh. Vui lòng hoàn tất thiết lập trước khi bật nó trong nhiều dự án. - setup_incomplete_header: Thiết lập lưu trữ chưa hoàn tất - error_invalid_provider_type: Vui lòng chọn một nhà cung cấp lưu trữ hợp lệ. + setup_incomplete_description: Bộ lưu trữ này có thiết lập chưa hoàn chỉnh. Vui lòng hoàn tất thiết lập trước khi kích hoạt nó trong nhiều dự án. + setup_incomplete_header: Thiết lập bộ nhớ chưa hoàn tất + error_invalid_provider_type: Vui lòng chọn nhà cung cấp bộ nhớ hợp lệ. file_storage_view: access_management: - automatic_management: Enable automatically-managed access and folders - automatic_management_description: Each project will be able to decide, when a storage is added to it, whether they want the automatically-managed or the manual approach to folder and access management. - description: OpenProject can automatically create and manage project folders when a file storage is added to a project. This can result in a more organized folder structure and straightforward access management that guarantees access to all relevant users. - manual_management: Only allow manually-managed access and folders - manual_management_description: Projects using this storage will not be offered the option of the automatically-managed approach. Folders and access must be managed manually. - setup_incomplete: Chọn loại quản lý quyền truy cập và tạo thư mục. - subtitle: Folder and access management - access_management_section: Quyền truy cập và thư mục dự án + automatic_management: Cho phép truy cập và thư mục được quản lý tự động + automatic_management_description: Mỗi dự án sẽ có thể quyết định, khi thêm bộ lưu trữ vào đó, liệu họ muốn quản lý thư mục và quyền truy cập theo cách được quản lý tự động hay thủ công. + description: OpenProject có thể tự động tạo và quản lý các thư mục dự án khi bộ lưu trữ tệp được thêm vào dự án. Điều này có thể dẫn đến cấu trúc thư mục có tổ chức hơn và quản lý quyền truy cập đơn giản hơn, đảm bảo quyền truy cập cho tất cả người dùng có liên quan. + manual_management: Chỉ cho phép truy cập và thư mục được quản lý thủ công + manual_management_description: Các dự án sử dụng bộ lưu trữ này sẽ không được cung cấp tùy chọn phương pháp quản lý tự động. Các thư mục và quyền truy cập phải được quản lý thủ công. + setup_incomplete: Chọn kiểu quản lý quyền truy cập của người dùng và tạo thư mục. + subtitle: Quản lý thư mục và quyền truy cập + access_management_section: Thư mục truy cập và dự án automatically_managed_folders: Thư mục được quản lý tự động general_information: Thông tin chung - oauth_configuration: OAuth configuration + oauth_configuration: Cấu hình OAuth one_drive: access_management: - automatic_management: Enable automatically-managed access and folders - automatic_management_description: Each project using this file storage will necessarily have to use the automatically-managed approach to folder and access management. They cannot do it manually. - manual_management: Only allow manually-managed access and folders - manual_management_description: Projects using this storage will not be offered the option of the automatically-managed approach. Folders and access must be managed manually. + automatic_management: Cho phép truy cập và thư mục được quản lý tự động + automatic_management_description: Mỗi dự án sử dụng bộ lưu trữ tệp này nhất thiết sẽ phải sử dụng phương pháp được quản lý tự động để quản lý thư mục và quyền truy cập. Họ không thể làm điều đó bằng tay. + manual_management: Chỉ cho phép truy cập và thư mục được quản lý thủ công + manual_management_description: Các dự án sử dụng bộ lưu trữ này sẽ không được cung cấp tùy chọn phương pháp quản lý tự động. Các thư mục và quyền truy cập phải được quản lý thủ công. one_drive_oauth: Azure OAuth - openproject_oauth: OpenProject OAuth + openproject_oauth: OAuth dự án mở project_folders: Thư mục dự án redirect_uri: URI chuyển hướng sharepoint_oauth: Azure OAuth - storage_audience_blank: No audience has been configured. - storage_audience_description: Exchanging tokens for audience "%{audience}". - storage_audience_idp: Using access token obtained by identity provider during login, regardless of audience. - storage_oauth: Storage OAuth + storage_audience_blank: Không có đối tượng nào được định cấu hình. + storage_audience_description: Trao đổi token cho khán giả "%{audience}". + storage_audience_idp: Sử dụng mã thông báo truy cập mà nhà cung cấp danh tính thu được trong quá trình đăng nhập, bất kể đối tượng. + storage_oauth: OAuth lưu trữ storage_provider: Nhà cung cấp lưu trữ - token_exchange: Token Exchange + token_exchange: Trao đổi mã thông báo health: actions: - download_report: Tải - rerun_checks: Re-run all checks - run_checks: Run checks now - checked: 'Last check: %{datetime}' + download_report: tải về + rerun_checks: Chạy lại tất cả các lần kiểm tra + run_checks: Chạy kiểm tra ngay bây giờ + checked: 'Lần kiểm tra cuối cùng: %{datetime}' checks: ampf_configuration: - client_folder_creation: Automatic folder creation - client_folder_removal: Automatic folder deletion - drive_contents: Drive content - files_request: Fetching team folder files - header: Các thư mục dự án được quản lý tự động - team_folder_app: 'Dependency: Team Folders' - team_folder_contents: Team folder content - team_folder_presence: Team folder exists - userless_access: Server-side request authentication + client_folder_creation: Tạo thư mục tự động + client_folder_removal: Tự động xóa thư mục + drive_contents: Nội dung ổ đĩa + files_request: Tải xuống các tệp trong thư mục của nhóm + header: Thư mục dự án được quản lý tự động + team_folder_app: 'Phụ thuộc: Thư mục nhóm' + team_folder_contents: Nội dung thư mục của nhóm + team_folder_presence: Thư mục nhóm đã tồn tại + userless_access: Xác thực yêu cầu phía máy chủ authentication: - existing_token: User token - header: Xác thực - non_provisioned_user: User provisioned by IDP - offline_access: Configured OIDC offline_access scope - provider_capabilities: Provider capabilities - provisioned_user_provider: User provisioned by OIDC - token_negotiable: User token capabilities - user_bound_request: User-based request authentication + existing_token: Mã thông báo người dùng + header: xác thực + non_provisioned_user: Người dùng được IDP cấp phép + offline_access: Đã định cấu hình phạm vi OIDC offline_access + provider_capabilities: Khả năng của nhà cung cấp + provisioned_user_provider: Người dùng được cung cấp bởi OIDC + token_negotiable: Khả năng mã thông báo người dùng + user_bound_request: Xác thực yêu cầu dựa trên người dùng base_configuration: - capabilities_request: Remote file storage capabilities - client_id: ID người dùng - client_secret: Khóa bí mật người dùng - dependencies_check: Phụ thuộc - dependencies_versions: Dependencies (version) - diagnostic_request: Diagnostic request - drive_id_exists: Drive exists - drive_id_format: Drive ID format - header: Configuration + capabilities_request: Khả năng lưu trữ tập tin từ xa + client_id: ID khách hàng + client_secret: Bí mật của khách hàng + dependencies_check: phụ thuộc + dependencies_versions: Phụ thuộc (phiên bản) + diagnostic_request: Yêu cầu chẩn đoán + drive_id_exists: Ổ đĩa tồn tại + drive_id_format: Định dạng ID ổ đĩa + header: Cấu hình host: URL máy chủ - host_url_accessible: Host URL accessible - storage_configured: Configuration complete - tenant_id: Tenant ID + host_url_accessible: URL máy chủ có thể truy cập được + storage_configured: Cấu hình hoàn tất + tenant_id: ID người thuê failures: - other: "%{count} checks failed" - success: Đã hoàn thành tất cả các bước kiểm tra + other: "%{count} kiểm tra không thành công" + success: Tất cả các cuộc kiểm tra đã được thông qua warnings: - other: "%{count} checks returned a warning" + other: "%{count} séc trả lại cảnh báo" connection_validation: - client_id_invalid: ID khách hàng OAuth 2 cấu hình không hợp lệ. Vui lòng kiểm tra cấu hình. - client_secret_invalid: Bí mật khách hàng OAuth 2 cấu hình không hợp lệ. Vui lòng kiểm tra cấu hình. - nc_dependency_missing: 'Thiếu sự phụ thuộc bắt buộc vào bộ lưu trữ tệp. Vui lòng thêm sự phụ thuộc sau: %{dependency}.' - nc_dependency_version_mismatch: The %{dependency} app version is not supported. Please update your Nextcloud server. - nc_host_not_found: Không tìm thấy máy chủ Nextcloud tại url máy chủ được cấu hình. Vui lòng kiểm tra cấu hình. - nc_oauth_request_not_found: The endpoint to fetch the currently connected user was not found. Please check the server logs for further information. - nc_oauth_request_unauthorized: The current user isn't authorized to access the remote file storage. Please check the server logs for further information. - nc_oauth_token_missing: OpenProject cannot test the user level communication with Nextcloud as the user did not yet link their Nextcloud account. - nc_team_folder_not_found: The team folder could not be found. - nc_unexpected_content: Unexpected content found in the managed team folder. - nc_userless_access_denied: The configured app password is invalid. - not_configured: Kết nối không thể được xác thực. Vui lòng hoàn tất cấu hình trước. - od_client_cant_delete_folder: The client is having trouble deleting folders. Please check the setup documentation for your storage. - od_client_write_permission_missing: The client seems to have write permissions missing. Please check the setup documentation for your storage. - od_drive_id_invalid: The configured drive id seems invalid. Please check the configuration. - od_drive_id_not_found: ID ổ đĩa cấu hình không tìm thấy. Vui lòng kiểm tra cấu hình. - od_oauth_request_not_found: The endpoint to fetch the currently connected user was not found. Please check the server logs for further information. - od_oauth_request_unauthorized: The current user isn't authorized to access the remote file storage. Please check the server logs for further information. - od_oauth_token_missing: OpenProject cannot test the user level communication with OneDrive as the user did not yet link their Microsoft account. - od_tenant_id_wrong: ID thư mục (người thuê) cấu hình không hợp lệ. Vui lòng kiểm tra cấu hình. - od_test_folder_exists: Thư mục %{folder_name} cần cho chức năng test đã tồn tại. Hãy xóa nó và thử lại. - od_unexpected_content: Nội dung không mong đợi được tìm thấy trong ổ đĩa. - offline_access_scope_missing: It is recommended to configure the OpenID Connect provider to request the offline_access scope. The integration may still work anyways, but make sure that refresh tokens do not expire. - oidc_cant_refresh_token: There was an error while trying to check your access to the storage. Please check the server logs for further information. - oidc_non_oidc_user: The current user, while provisioned, wasn't provisioned by an OpenID Connect (OIDC) Identity Provider. Please re-run the check with an OIDC provisioned user. - oidc_non_provisioned_user: The current user isn't provided by an OpenID Connect Identity Provider. Please re-run the check with a provided user. - oidc_provider_cant_exchange: The OpenID Connect provider does not seem to support token exchange, but token exchange was configured for the storage. - oidc_token_acquisition_failed: - oidc_token_exchange_failed: There seems to be a problem with the Token Exchange setup on your OpenID Connect Provider. Please check its configuration and try again. - oidc_token_refresh_failed: There was an error while trying to check your access to the storage. Please check the server logs for further information. - sp_client_cant_delete_folder: The client is having trouble deleting folders in SharePoint. Please check the setup documentation for your storage. - sp_client_id_missing: The configured OAuth 2 client id is missing for SharePoint. Please check the configuration. - sp_client_secret_missing: The configured OAuth 2 client secret is missing for SharePoint. Please check the configuration. - sp_client_write_permission_missing: The client seems to have write permissions missing in SharePoint. Please check the setup documentation for your storage. - sp_existing_test_folder: The folder %{folder_name} needed for testing already exists in SharePoint. Please delete it and try again. - sp_oauth_request_error: The user-bound request to SharePoint failed. Please check the server logs for further information. - sp_oauth_request_unauthorized: The current user isn't authorized to access the remote file storage. Please check the server logs for further information. - sp_oauth_token_missing: OpenProject cannot test the user level communication with SharePoint as the user did not yet link their SharePoint account. - sp_tenant_id_missing: The configured directory (tenant) id is missing for SharePoint. Please check the configuration. - sp_unexpected_content: Unexpected content found in the SharePoint Document Library. - unknown_error: Kết nối không thể được xác thực. Đã xảy ra lỗi không xác định. Vui lòng kiểm tra nhật ký máy chủ để biết thêm thông tin. - label_error: Lỗi - label_failed: Failed - label_healthy: Khỏe - label_passed: Passed - label_pending: Chờ xử lý - label_skipped: Đã bỏ qua + client_id_invalid: Id ứng dụng khách OAuth 2 đã định cấu hình không hợp lệ. Vui lòng kiểm tra cấu hình. + client_secret_invalid: Bí mật ứng dụng khách OAuth 2 đã định cấu hình không hợp lệ. Vui lòng kiểm tra cấu hình. + nc_dependency_missing: 'Thiếu phần phụ thuộc bắt buộc trên bộ lưu trữ tệp. Vui lòng thêm phần phụ thuộc sau: %{dependency}.' + nc_dependency_version_mismatch: Phiên bản ứng dụng %{dependency} không được hỗ trợ. Vui lòng cập nhật máy chủ Nextcloud của bạn. + nc_host_not_found: Không tìm thấy máy chủ Nextcloud tại url máy chủ đã định cấu hình. Vui lòng kiểm tra cấu hình. + nc_oauth_request_not_found: Không tìm thấy điểm cuối để tìm nạp người dùng hiện được kết nối. Vui lòng kiểm tra nhật ký máy chủ để biết thêm thông tin. + nc_oauth_request_unauthorized: Người dùng hiện tại không được phép truy cập vào bộ lưu trữ tệp từ xa. Vui lòng kiểm tra nhật ký máy chủ để biết thêm thông tin. + nc_oauth_token_missing: OpenProject không thể kiểm tra giao tiếp ở cấp độ người dùng với Nextcloud vì người dùng chưa liên kết tài khoản Nextcloud của họ. + nc_team_folder_not_found: Thư mục nhóm không thể được tìm thấy. + nc_unexpected_content: Nội dung không mong đợi được tìm thấy trong thư mục của nhóm được quản lý. + nc_userless_access_denied: Mật khẩu ứng dụng đã định cấu hình không hợp lệ. + not_configured: Kết nối không thể được xác nhận. Vui lòng hoàn tất cấu hình trước. + od_client_cant_delete_folder: Máy khách đang gặp sự cố khi xóa các thư mục. Vui lòng kiểm tra tài liệu thiết lập cho bộ nhớ của bạn. + od_client_write_permission_missing: Máy khách dường như bị thiếu quyền ghi. Vui lòng kiểm tra tài liệu thiết lập cho bộ nhớ của bạn. + od_drive_id_invalid: Id ổ đĩa được cấu hình có vẻ không hợp lệ. Vui lòng kiểm tra cấu hình. + od_drive_id_not_found: Không thể tìm thấy id ổ đĩa được cấu hình. Vui lòng kiểm tra cấu hình. + od_oauth_request_not_found: Không tìm thấy điểm cuối để tìm nạp người dùng hiện được kết nối. Vui lòng kiểm tra nhật ký máy chủ để biết thêm thông tin. + od_oauth_request_unauthorized: Người dùng hiện tại không được phép truy cập vào bộ lưu trữ tệp từ xa. Vui lòng kiểm tra nhật ký máy chủ để biết thêm thông tin. + od_oauth_token_missing: OpenProject không thể kiểm tra giao tiếp ở cấp độ người dùng với OneDrive vì người dùng chưa liên kết tài khoản Microsoft của họ. + od_tenant_id_wrong: Id thư mục (đối tượng thuê) được định cấu hình không hợp lệ. Vui lòng kiểm tra cấu hình. + od_test_folder_exists: Thư mục %{folder_name} cần thiết để kiểm tra đã tồn tại. Vui lòng xóa nó và thử lại. + od_unexpected_content: Đã tìm thấy nội dung không mong đợi trong ổ đĩa. + offline_access_scope_missing: Bạn nên định cấu hình nhà cung cấp OpenID Connect để yêu cầu phạm vi offline_access. Dù sao đi nữa, quá trình tích hợp vẫn có thể hoạt động nhưng hãy đảm bảo rằng mã thông báo làm mới không hết hạn. + oidc_cant_refresh_token: Đã xảy ra lỗi khi cố kiểm tra quyền truy cập của bạn vào bộ lưu trữ. Vui lòng kiểm tra nhật ký máy chủ để biết thêm thông tin. + oidc_non_oidc_user: Người dùng hiện tại, mặc dù được cấp phép, nhưng lại không được Nhà cung cấp danh tính OpenID Connect (OIDC) cấp phép. Vui lòng chạy lại quá trình kiểm tra với người dùng được cung cấp OIDC. + oidc_non_provisioned_user: Người dùng hiện tại không được cung cấp bởi Nhà cung cấp nhận dạng OpenID Connect. Vui lòng chạy lại quá trình kiểm tra với người dùng được cung cấp. + oidc_provider_cant_exchange: Nhà cung cấp OpenID Connect dường như không hỗ trợ trao đổi mã thông báo nhưng trao đổi mã thông báo đã được định cấu hình cho bộ lưu trữ. + oidc_token_acquisition_failed: Thiết lập OpenID Connect của bạn không cung cấp đối tượng cần thiết cũng như không cung cấp khả năng trao đổi mã thông báo. Vui lòng xem tài liệu của chúng tôi để biết thêm thông tin. + oidc_token_exchange_failed: Có vẻ như đã xảy ra sự cố với thiết lập Trao đổi mã thông báo trên Nhà cung cấp kết nối OpenID của bạn. Vui lòng kiểm tra cấu hình của nó và thử lại. + oidc_token_refresh_failed: Đã xảy ra lỗi khi cố kiểm tra quyền truy cập của bạn vào bộ lưu trữ. Vui lòng kiểm tra nhật ký máy chủ để biết thêm thông tin. + sp_client_cant_delete_folder: Máy khách đang gặp sự cố khi xóa thư mục trong SharePoint. Vui lòng kiểm tra tài liệu thiết lập cho bộ nhớ của bạn. + sp_client_id_missing: Thiếu id ứng dụng khách OAuth 2 đã định cấu hình cho SharePoint. Vui lòng kiểm tra cấu hình. + sp_client_secret_missing: Thiếu bí mật ứng dụng khách OAuth 2 đã định cấu hình cho SharePoint. Vui lòng kiểm tra cấu hình. + sp_client_write_permission_missing: Máy khách dường như bị thiếu quyền ghi trong SharePoint. Vui lòng kiểm tra tài liệu thiết lập cho bộ nhớ của bạn. + sp_existing_test_folder: Thư mục %{folder_name} cần thiết để kiểm tra đã tồn tại trong SharePoint. Vui lòng xóa nó và thử lại. + sp_oauth_request_error: Yêu cầu do người dùng giới hạn đối với SharePoint không thành công. Vui lòng kiểm tra nhật ký máy chủ để biết thêm thông tin. + sp_oauth_request_unauthorized: Người dùng hiện tại không được phép truy cập vào bộ lưu trữ tệp từ xa. Vui lòng kiểm tra nhật ký máy chủ để biết thêm thông tin. + sp_oauth_token_missing: OpenProject không thể kiểm tra giao tiếp ở cấp độ người dùng với SharePoint vì người dùng chưa liên kết tài khoản SharePoint của họ. + sp_tenant_id_missing: Thiếu id thư mục (đối tượng thuê) được cấu hình cho SharePoint. Vui lòng kiểm tra cấu hình. + sp_unexpected_content: Nội dung không mong muốn được tìm thấy trong Thư viện Tài liệu SharePoint. + unknown_error: Kết nối không thể được xác nhận. Đã xảy ra lỗi không xác định. Vui lòng kiểm tra nhật ký máy chủ để biết thêm thông tin. + label_error: lỗi + label_failed: thất bại + label_healthy: khỏe mạnh + label_passed: đã vượt qua + label_pending: đang chờ xử lý + label_skipped: bỏ qua label_warning: Cảnh báo - no_report: No report available - no_report_description: Run the checks now for a full health status report for this file storage. - open_report: Open full health report + no_report: Không có báo cáo nào + no_report_description: Hãy chạy kiểm tra ngay bây giờ để có báo cáo trạng thái đầy đủ cho bộ lưu trữ tệp này. + open_report: Mở báo cáo sức khỏe đầy đủ project_folders: subtitle: Các thư mục dự án được quản lý tự động since: kể từ %{datetime} summary: - failure: Some checks failed and the system does not work as expected. - success: All connections and systems are working as expected. - warning: Some checks returned a warning. This can lead to unexpected behaviour. - title: Health status report + failure: Một số lần kiểm tra không thành công và hệ thống không hoạt động như mong đợi. + success: Tất cả các kết nối và hệ thống đều hoạt động như mong đợi. + warning: Một số kiểm tra trả lại một cảnh báo. Điều này có thể dẫn đến hành vi không mong muốn. + title: Báo cáo tình trạng sức khoẻ health_email_notifications: - description_disabled: Admins will not receive updates by email when there are important updates. - description_enabled: Admins will receive updates by email when there are important updates. - error_could_not_be_saved: Cài đặt thông báo qua email không thể được lưu. Vui lòng thử lại. - title: Email updates to admins + description_disabled: Quản trị viên sẽ không nhận được thông tin cập nhật qua email khi có thông tin cập nhật quan trọng. + description_enabled: Quản trị viên sẽ nhận được thông tin cập nhật qua email khi có thông tin cập nhật quan trọng. + error_could_not_be_saved: Không thể lưu cài đặt thông báo qua email. Vui lòng thử lại. + title: Cập nhật qua email cho quản trị viên help_texts: - project_folder: Thư mục dự án là thư mục mặc định để tải lên tệp cho dự án này. Người dùng vẫn có thể tải lên tệp đến các vị trí khác. - project_folder_bulk: Thư mục dự án là thư mục mặc định để tải tệp lên cho tất cả các dự án đã chọn. Bạn có thể sửa đổi thư mục này riêng lẻ trong từng cài đặt dự án. Tuy nhiên, người dùng vẫn có thể tải tệp lên các vị trí khác. + project_folder: Thư mục dự án là thư mục mặc định để tải lên tệp cho dự án này. Tuy nhiên, người dùng vẫn có thể tải tệp lên các vị trí khác. + project_folder_bulk: Thư mục dự án là thư mục mặc định để tải lên tệp cho tất cả các dự án đã chọn. Bạn có thể sửa đổi điều này riêng lẻ trong từng cài đặt dự án. Tuy nhiên, người dùng vẫn có thể tải tệp lên các vị trí khác. instructions: - all_available_storages_already_added: Tất cả các lưu trữ có sẵn đã được thêm vào dự án. - authentication_method: The way that requests between OpenProject and the Storage are authenticated. - automatic_folder: Điều này sẽ tự động tạo một thư mục gốc cho dự án này và quản lý quyền truy cập cho từng thành viên của dự án. + all_available_storages_already_added: Tất cả các kho lưu trữ có sẵn đã được thêm vào dự án. + authentication_method: Cách thức xác thực các yêu cầu giữa OpenProject và Storage. + automatic_folder: Điều này sẽ tự động tạo thư mục gốc cho dự án này và quản lý quyền truy cập cho từng thành viên dự án. empty_project_folder_validation: Việc chọn một thư mục là bắt buộc để tiếp tục. - existing_manual_folder: Bạn có thể chỉ định một thư mục hiện có làm thư mục gốc cho dự án này. Tuy nhiên, quyền truy cập không được quản lý tự động, quản trị viên cần đảm bảo rằng các người dùng liên quan có quyền truy cập. Thư mục đã chọn có thể được sử dụng bởi nhiều dự án. - host: Vui lòng thêm địa chỉ máy chủ lưu trữ của bạn bao gồm https://. Địa chỉ không nên dài quá 255 ký tự. - managed_project_folders_application_password_caption: 'Kích hoạt các thư mục được quản lý tự động bằng cách sao chép giá trị này từ: %{provider_type_link}.' - name: Đặt tên cho lưu trữ của bạn để người dùng có thể phân biệt giữa nhiều lưu trữ. + existing_manual_folder: Bạn có thể chỉ định một thư mục hiện có làm thư mục gốc cho dự án này. Tuy nhiên, các quyền không được quản lý tự động, quản trị viên cần đảm bảo người dùng liên quan có quyền truy cập theo cách thủ công. Thư mục đã chọn có thể được sử dụng bởi nhiều dự án. + host: Vui lòng thêm địa chỉ máy chủ lưu trữ của bạn bao gồm https://. Nó không được dài hơn 255 ký tự. + managed_project_folders_application_password_caption: 'Bật các thư mục được quản lý tự động bằng cách sao chép giá trị này từ: %{provider_type_link}.' + name: Đặt tên cho bộ lưu trữ của bạn để người dùng có thể phân biệt giữa nhiều bộ lưu trữ. new_storage: Đọc tài liệu của chúng tôi về cài đặt tích hợp lưu trữ tệp %{provider_name} để biết thêm thông tin. nextcloud: application_link_text: ứng dụng “Tích hợp OpenProject” integration: Quản trị Nextcloud / OpenProject oauth_configuration: Sao chép các giá trị này từ %{application_link_text}. - provider_configuration: Vui lòng đảm bảo bạn có quyền quản trị trong phiên bản Nextcloud của bạn và ứng dụng %{application_link_text} đã được cài đặt trước khi thực hiện cấu hình. - storage_audience: The client ID that the Nextcloud instance uses to communicate with the identity provider. - storage_audience_placeholder: e.g. nextcloud - token_exchange_scope: The scopes that should be requested during token exchange, each one separated by a space. - no_specific_folder: Theo mặc định, mỗi người dùng sẽ bắt đầu tại thư mục cá nhân của họ khi tải lên tệp. - no_storage_set_up: Chưa có lưu trữ tệp nào được thiết lập. + provider_configuration: Vui lòng đảm bảo rằng bạn có đặc quyền quản trị trong phiên bản Nextcloud của mình và %{application_link_text} được cài đặt trước khi thực hiện thiết lập. + storage_audience: ID khách hàng mà phiên bản Nextcloud sử dụng để liên lạc với nhà cung cấp danh tính. + storage_audience_placeholder: ví dụ. đám mây tiếp theo + token_exchange_scope: Các phạm vi cần được yêu cầu trong quá trình trao đổi mã thông báo, mỗi phạm vi được phân tách bằng dấu cách. + no_specific_folder: Theo mặc định, mỗi người dùng sẽ bắt đầu tại thư mục chính của riêng họ khi họ tải tệp lên. + no_storage_set_up: Chưa có kho lưu trữ tập tin nào được thiết lập. not_logged_into_storage: Để chọn thư mục dự án, vui lòng đăng nhập trước - oauth_application_details: Giá trị bí mật khách hàng sẽ không còn khả dụng sau khi bạn đóng cửa sổ này. Vui lòng sao chép các giá trị này vào %{oauth_application_details_link}. - oauth_application_details_link_text: Cài đặt Tích hợp OpenProject Nextcloud + oauth_application_details: Giá trị bí mật của máy khách sẽ không thể truy cập lại được sau khi bạn đóng cửa sổ này. Vui lòng sao chép các giá trị này vào %{oauth_application_details_link}. + oauth_application_details_link_text: Cài đặt tích hợp Nextcloud OpenProject one_drive: - application_link_text: cổng thông tin Azure + application_link_text: Cổng thông tin Azure copy_redirect_uri: Sao chép URI chuyển hướng - documentation_link_text: OneDrive file storages documentation + documentation_link_text: Tài liệu lưu trữ tệp OneDrive drive_id: Vui lòng sao chép ID từ ổ đĩa mong muốn bằng cách làm theo các bước trong %{drive_id_link_text}. integration: OneDrive - missing_client_id_for_redirect_uri: Vui lòng điền các giá trị OAuth để tạo một URI - oauth_client_redirect_uri: Vui lòng sao chép giá trị này vào một URI chuyển hướng Web mới dưới Redirect URIs. - oauth_client_secret: Trong trường hợp không có bí mật khách hàng ứng dụng dưới Client credentials, vui lòng tạo một cái mới. + missing_client_id_for_redirect_uri: Vui lòng điền các giá trị OAuth để tạo URI + oauth_client_redirect_uri: Vui lòng sao chép giá trị này vào URI chuyển hướng Web mới trong URI chuyển hướng. + oauth_client_secret: Trong trường hợp không có bí mật ứng dụng khách nào trong Thông tin xác thực ứng dụng khách, vui lòng tạo một mật khẩu mới. oauth_configuration: Sao chép các giá trị này từ ứng dụng mong muốn trong %{application_link_text}. - provider_configuration: Vui lòng đảm bảo bạn có quyền quản trị trong %{application_link_text} hoặc liên hệ với quản trị viên Microsoft của bạn trước khi thực hiện cấu hình. Trong cổng thông tin, bạn cũng cần đăng ký một ứng dụng Azure hoặc sử dụng một cái hiện có để xác thực. - tenant_id: Vui lòng sao chép ID Thư mục (người thuê) từ ứng dụng mong muốn và Đăng ký ứng dụng trong %{application_link_text}. + provider_configuration: Vui lòng đảm bảo rằng bạn có đặc quyền quản trị trong %{application_link_text} hoặc liên hệ với quản trị viên Microsoft của bạn trước khi thực hiện thiết lập. Trong cổng thông tin, bạn cũng cần đăng ký ứng dụng Azure hoặc sử dụng ứng dụng hiện có để xác thực. + tenant_id: Vui lòng sao chép ID Thư mục (người thuê) từ ứng dụng mong muốn và đăng ký Ứng dụng trong %{application_link_text}. tenant_id_placeholder: Tên hoặc UUID - setting_up_additional_storages: Để thiết lập các lưu trữ tệp bổ sung, vui lòng truy cập - setting_up_additional_storages_non_admin: Administrators can set up additional file storages in Administration / File storages. - setting_up_storages: Để thiết lập lưu trữ tệp, vui lòng truy cập - setting_up_storages_non_admin: Administrators can set up file storages in Administration / File storages. + setting_up_additional_storages: Để thiết lập kho lưu trữ tệp bổ sung, vui lòng truy cập + setting_up_additional_storages_non_admin: Quản trị viên có thể thiết lập kho lưu trữ tệp bổ sung trong Quản trị / Kho lưu trữ tệp. + setting_up_storages: Để thiết lập kho lưu trữ tập tin, vui lòng truy cập + setting_up_storages_non_admin: Quản trị viên có thể thiết lập kho lưu trữ tệp trong Quản trị / Kho lưu trữ tệp. sharepoint: - application_link_text: SharePoint site - integration: SharePoint Administration / OpenProject + application_link_text: Trang SharePoint + integration: Quản trị SharePoint/OpenProject oauth_configuration: Sao chép các giá trị này từ ứng dụng mong muốn trong %{application_link_text}. - provider_configuration: Vui lòng đảm bảo bạn có quyền quản trị trong %{application_link_text} hoặc liên hệ với quản trị viên Microsoft của bạn trước khi thực hiện cấu hình. Trong cổng thông tin, bạn cũng cần đăng ký một ứng dụng Azure hoặc sử dụng một cái hiện có để xác thực. - type: 'Vui lòng đảm bảo bạn có quyền quản trị trong phiên bản Nextcloud của bạn và đã cài đặt ứng dụng sau đây trước khi thực hiện cấu hình:' - type_link_text: "“Tích hợp OpenProject”" + provider_configuration: Vui lòng đảm bảo rằng bạn có đặc quyền quản trị trong %{application_link_text} hoặc liên hệ với quản trị viên Microsoft của bạn trước khi thực hiện thiết lập. Trong cổng thông tin, bạn cũng cần đăng ký ứng dụng Azure hoặc sử dụng ứng dụng hiện có để xác thực. + type: 'Vui lòng đảm bảo rằng bạn có đặc quyền quản trị trong phiên bản Nextcloud của mình và cài đặt ứng dụng sau trước khi thực hiện thiết lập:' + type_link_text: "“Dự án mở tích hợp”" label_active: Đang hoạt động - label_add_new_storage: Thêm lưu trữ mới - label_automatic_folder: Thư mục mới với quyền quản lý tự động - label_creation_time: Thời gian tạo - label_creator: Người tạo - label_delete_storage: Xóa lưu trữ - label_edit_storage_access_management: Chỉnh sửa quản lý quyền truy cập lưu trữ - label_edit_storage_automatically_managed_folders: Chỉnh sửa thư mục được quản lý tự động của lưu trữ + label_add_new_storage: Thêm bộ nhớ mới + label_automatic_folder: Thư mục mới với quyền được quản lý tự động + label_creation_time: Thời gian sáng tạo + label_creator: Người sáng tạo + label_delete_storage: Xóa bộ nhớ + label_edit_storage_access_management: Chỉnh sửa quản lý quyền truy cập bộ nhớ + label_edit_storage_automatically_managed_folders: Chỉnh sửa các thư mục được quản lý tự động lưu trữ label_edit_storage_host: Chỉnh sửa máy chủ lưu trữ - label_edit_token_exchange: Edit token exchange configuration - label_existing_manual_folder: Thư mục hiện có với quyền quản lý thủ công - label_file_storage: Lưu trữ tệp + label_edit_token_exchange: Chỉnh sửa cấu hình trao đổi mã thông báo + label_existing_manual_folder: Thư mục hiện có với quyền được quản lý thủ công + label_file_storage: Lưu trữ tập tin label_host: URL máy chủ label_inactive: Không hoạt động label_managed_project_folders: application_password: Mật khẩu ứng dụng automatically_managed_folders: Thư mục được quản lý tự động - label_name: Tên - label_new_file_storage: Lưu trữ tệp %{provider} mới - label_new_storage: Lưu trữ mới - label_no_selected_folder: Không có thư mục được chọn + label_name: tên + label_new_file_storage: Bộ nhớ %{provider} mới + label_new_storage: Bộ nhớ mới + label_no_selected_folder: Không có thư mục nào được chọn label_no_specific_folder: Không có thư mục cụ thể - label_oauth_client_id: ID Khách hàng OAuth - label_openproject_oauth_application_id: ID Khách hàng OAuth OpenProject - label_openproject_oauth_application_secret: Bí mật khách hàng OAuth OpenProject + label_oauth_client_id: ID ứng dụng khách OAuth + label_openproject_oauth_application_id: ID khách hàng OpenProject OAuth + label_openproject_oauth_application_secret: Bí mật ứng dụng khách OpenProject OAuth label_project_folder: Thư mục dự án - label_provider: Nhà cung cấp + label_provider: nhà cung cấp label_redirect_uri: URI chuyển hướng label_show_storage_redirect_uri: Hiển thị URI chuyển hướng - label_status: Trạng thái + label_status: trạng thái label_storage: Lưu trữ label_uri: URI member_connection_status: - connected: Đã kết nối + connected: đã kết nối connected_no_permissions: Vai trò người dùng không có quyền lưu trữ - not_connectable: Not connectable. The storage requires login through an SSO provider, but the user is not logging in through SSO. - not_connected: Chưa kết nối. Người dùng nên đăng nhập vào lưu trữ qua %{link}. - not_connected_sso: Not yet connected, SSO should automatically connect them, once looking at files. - members_no_results: Không có thành viên nào để hiển thị. - no_results: Chưa có lưu trữ nào được thiết lập. + not_connectable: Không thể kết nối được. Bộ lưu trữ yêu cầu đăng nhập thông qua nhà cung cấp SSO nhưng người dùng không đăng nhập qua SSO. + not_connected: Không được kết nối. Người dùng nên đăng nhập vào bộ lưu trữ thông qua %{link} sau. + not_connected_sso: Chưa được kết nối, SSO sẽ tự động kết nối chúng sau khi xem tệp. + members_no_results: Không có thành viên nào để hiển thị + no_results: Chưa có kho lưu trữ nào được thiết lập. oauth_access_granted_modal: access_granted: Quyền truy cập đã được cấp project_settings: - access_granted_screen_reader: Đã cấp quyền truy cập. Bây giờ bạn đã sẵn sàng sử dụng %{storage} + access_granted_screen_reader: Quyền truy cập được cấp. Bây giờ bạn đã sẵn sàng sử dụng %{storage} storage_ready: Bạn đã sẵn sàng sử dụng %{storage} storage_admin: - access_granted_screen_reader: Đã cấp quyền truy cập. Bây giờ bạn đã sẵn sàng để thêm các dự án vào %{storage} - storage_ready: Bây giờ bạn đã sẵn sàng để thêm dự án vào %{storage} + access_granted_screen_reader: Quyền truy cập được cấp. Bây giờ bạn đã sẵn sàng thêm dự án vào %{storage} + storage_ready: Bây giờ bạn đã sẵn sàng thêm dự án vào %{storage} oauth_grant_nudge_modal: - cancel_button_label: Tôi sẽ làm việc đó sau - heading: Cần phải đăng nhập vào %{provider_type} + cancel_button_label: tôi sẽ làm nó sau + heading: Yêu cầu đăng nhập vào %{provider_type} login_button_aria_label: Đăng nhập vào %{storage} login_button_label: "%{provider_type} đăng nhập" project_settings: description: Để có quyền truy cập vào thư mục dự án, bạn cần đăng nhập vào %{storage}. requesting_access_to: Đang yêu cầu quyền truy cập vào %{storage} storage_admin: - description: Để thêm dự án vào kho lưu trữ này, bạn cần phải đăng nhập vào %{provider_type}. Vui lòng đăng nhập và thử lại. + description: Để thêm dự án vào bộ lưu trữ này, bạn cần phải đăng nhập vào %{provider_type}. Vui lòng đăng nhập và thử lại. open_project_storage_modal: success: subtitle: Bạn đang được chuyển hướng - title: Cấu hình tích hợp đã hoàn tất + title: Thiết lập tích hợp đã hoàn tất timeout: - link_text: health status of the file storage setup - subtitle: OpenProject could not provide you access to the project folder within the expected period of time. Please, try once again.

    If that problem persists please contact your OpenProject administrator to check %{storages_health_link}. + link_text: tình trạng sức khỏe của thiết lập lưu trữ tệp + subtitle: OpenProject không thể cung cấp cho bạn quyền truy cập vào thư mục dự án trong khoảng thời gian dự kiến. Vui lòng thử lại một lần nữa.

    Nếu sự cố đó vẫn tiếp diễn, vui lòng liên hệ với quản trị viên OpenProject của bạn để kiểm tra %{storages_health_link}. waiting: - subtitle: Một chút thời gian, điều này có thể mất một chút thời gian... + subtitle: Xin vui lòng chờ một chút, việc này có thể mất chút thời gian... title: Chúng tôi đang thiết lập quyền của bạn trên thư mục dự án. page_titles: file_storages: - delete: Delete file storage? - subtitle: Thêm một lưu trữ tệp bên ngoài để tải lên, liên kết và quản lý tệp trong các gói công việc. + delete: Xóa bộ nhớ tập tin? + subtitle: Thêm bộ lưu trữ tệp bên ngoài để tải lên, liên kết và quản lý tệp trong gói công việc. managed_project_folders: subtitle: |- - Để OpenProject tạo các thư mục theo dự án tự động. Điều này được khuyến nghị vì nó đảm bảo rằng mọi thành viên trong nhóm luôn có quyền truy cập chính xác. - subtitle_short: Để OpenProject tạo các thư mục theo dự án tự động. + Hãy để OpenProject tự động tạo các thư mục cho mỗi dự án. Điều này được khuyến khích vì nó đảm bảo rằng mọi đội + thành viên luôn có quyền truy cập chính xác. + subtitle_short: Hãy để OpenProject tự động tạo các thư mục cho mỗi dự án. title: Thư mục dự án được quản lý tự động project_settings: - edit: Chỉnh sửa lưu trữ tệp cho dự án này - members_connection_status: Trạng thái kết nối của các thành viên - new: Thêm một lưu trữ tệp vào dự án này + edit: Chỉnh sửa lưu trữ tập tin cho dự án này + members_connection_status: Trạng thái kết nối thành viên + new: Thêm nơi lưu trữ tệp vào dự án này project_storage_members: - subtitle: Kiểm tra trạng thái kết nối của lưu trữ %{storage_name_link} đối với tất cả các thành viên dự án. - title: Trạng thái kết nối của các thành viên + subtitle: Kiểm tra trạng thái kết nối cho bộ lưu trữ %{storage_name_link} của tất cả các thành viên dự án. + title: Trạng thái kết nối thành viên project_storages: - delete: Remove file storage from project? - permission_header_explanation: 'Quyền trên các lưu trữ bên ngoài chỉ được áp dụng cho các thư mục và tệp trong các thư mục dự án được quản lý tự động. Lưu ý rằng không phải tất cả các quyền tệp đều được hỗ trợ bởi tất cả các nhà cung cấp lưu trữ. Vui lòng kiểm tra tài liệu về quyền lưu trữ tệp để biết thêm thông tin.' + delete: Xóa bộ nhớ tệp khỏi dự án? + permission_header_explanation: 'Quyền đối với tệp trên bộ lưu trữ bên ngoài chỉ được áp dụng cho các thư mục và tệp trong thư mục dự án được quản lý tự động. Lưu ý rằng không phải tất cả các quyền đối với tệp đều được tất cả các nhà cung cấp dịch vụ lưu trữ hỗ trợ. Vui lòng kiểm tra tài liệu về quyền lưu trữ tệp để biết thêm thông tin.' provider_types: label: Loại nhà cung cấp nextcloud: - label_oauth_client_id: ID Khách hàng OAuth Nextcloud - label_oauth_client_secret: Bí mật khách hàng OAuth Nextcloud + label_oauth_client_id: ID khách hàng OAuth của Nextcloud + label_oauth_client_secret: Bí mật ứng dụng khách Nextcloud OAuth name: Nextcloud - name_placeholder: 'ví dụ: Nextcloud' + name_placeholder: ví dụ. Nextcloud one_drive: - label_oauth_client_id: ID Ứng dụng OAuth Azure (khách hàng) - label_oauth_client_secret: Giá trị Bí mật khách hàng OAuth Azure + label_oauth_client_id: ID ứng dụng Azure OAuth (ứng dụng khách) + label_oauth_client_secret: Giá trị bí mật của ứng dụng khách Azure OAuth name: OneDrive - name_placeholder: 'ví dụ: OneDrive' + name_placeholder: ví dụ. OneDrive sharepoint: - drive_description: OpenProject access-managed document library - label_oauth_client_id: ID Ứng dụng OAuth Azure (khách hàng) - label_oauth_client_secret: Giá trị Bí mật khách hàng OAuth Azure + drive_description: Thư viện tài liệu được quản lý truy cập OpenProject + label_oauth_client_id: ID ứng dụng Azure OAuth (ứng dụng khách) + label_oauth_client_secret: Giá trị bí mật của ứng dụng khách Azure OAuth name: SharePoint - name_placeholder: e.g. SharePoint + name_placeholder: ví dụ. SharePoint show_attachments_toggle: - description: Deactivating this option will hide the attachments list on the work packages files tab. The files attached in the description of a work package will still be uploaded in the internal attachments storage. - label: Hiển thị tệp đính kèm trong tab tệp các gói công việc + description: Việc tắt tùy chọn này sẽ ẩn danh sách tệp đính kèm trên tab tệp gói công việc. Các tệp đính kèm trong phần mô tả của gói công việc sẽ vẫn được tải lên bộ lưu trữ tệp đính kèm nội bộ. + label: Hiển thị tệp đính kèm trong tab tệp gói công việc storage_audience: - documentation_intro: Please read our documentation for details on the following options and configuration of the identity provider. + documentation_intro: Vui lòng đọc tài liệu của chúng tôi để biết chi tiết về các tùy chọn và cấu hình sau đây của nhà cung cấp danh tính. idp: - helptext: OpenProject will use the access token received by the identity provider during log in to authenticate requests to the storage. It will not try to obtain another token. - label: Use access token obtained during user log in + helptext: OpenProject sẽ sử dụng mã thông báo truy cập mà nhà cung cấp danh tính nhận được trong quá trình đăng nhập để xác thực các yêu cầu tới bộ lưu trữ. Nó sẽ không cố gắng lấy một mã thông báo khác. + label: Sử dụng mã thông báo truy cập thu được trong quá trình đăng nhập của người dùng manual: - helptext: OpenProject will exchange a token with the identity provider for the given audience. - label: Manually specify audience for which to exchange access token (Recommended) + helptext: OpenProject sẽ trao đổi mã thông báo với nhà cung cấp danh tính cho đối tượng nhất định. + label: Chỉ định thủ công đối tượng cần trao đổi mã thông báo truy cập (Được khuyến nghị) storage_list_blank_slate: - description: Thêm một lưu trữ để thấy chúng ở đây. - heading: Bạn chưa có lưu trữ nào. - successful_storage_connection: Storage connected successfully! Remember to activate the storage in the Projects tab for each desired project to use it. + description: Thêm một bộ lưu trữ để xem chúng ở đây. + heading: Bạn chưa có bất kỳ kho lưu trữ nào. + successful_storage_connection: Đã kết nối bộ nhớ thành công! Hãy nhớ kích hoạt bộ nhớ trong tab Dự án cho từng dự án mong muốn sử dụng. upsell: one_drive: description: |- - Integrate your OneDrive drive as a file storage with OpenProject. Upload files and link them directly to - work packages in a project. - title: OneDrive integration + Tích hợp ổ OneDrive của bạn làm nơi lưu trữ tệp với OpenProject. Tải tập tin lên và liên kết chúng trực tiếp với + các gói công việc trong một dự án. + title: Tích hợp OneDrive sharepoint: description: |- - Integrate your SharePoint site as a file storage with OpenProject. Upload files and link them directly to - work packages in a project. - title: SharePoint integration + Tích hợp trang SharePoint của bạn dưới dạng nơi lưu trữ tệp với OpenProject. Tải tập tin lên và liên kết chúng trực tiếp với + các gói công việc trong một dự án. + title: Tích hợp SharePoint diff --git a/modules/storages/config/locales/crowdin/zh-CN.yml b/modules/storages/config/locales/crowdin/zh-CN.yml index 54414079b0f..32c11f78e9c 100644 --- a/modules/storages/config/locales/crowdin/zh-CN.yml +++ b/modules/storages/config/locales/crowdin/zh-CN.yml @@ -104,20 +104,20 @@ zh-CN: create_folder: '托管项目文件夹创建:' ensure_root_folder_permissions: '设置基础文件夹权限:' hide_inactive_folders: '隐藏非活动文件夹步骤:' - remote_folders: 'Read contents of the team folder:' + remote_folders: '读取团队文件夹的内容:' remove_user_from_group: '从组中移除用户' rename_project_folder: '重命名托管项目文件夹:' one_drive_sync_service: create_folder: '托管项目文件夹创建:' ensure_root_folder_permissions: '设置基础文件夹权限:' hide_inactive_folders: '隐藏非活动文件夹步骤:' - remote_folders: 'Read contents of the drive root folder:' + remote_folders: '读取驱动器根文件夹的内容:' rename_project_folder: '重命名托管项目文件夹:' sharepoint_sync_service: create_folder: '托管项目文件夹创建:' ensure_root_folder_permissions: '设置基础文件夹权限:' hide_inactive_folders: '隐藏非活动文件夹步骤:' - remote_folders: 'Read contents of the drive root folder:' + remote_folders: '读取驱动器根文件夹的内容:' rename_project_folder: '重命名托管项目文件夹:' errors: messages: @@ -140,7 +140,7 @@ zh-CN: conflict: 文件夹 %{folder_name} 已经存在于 %{parent_location} 上。 not_found: "未找到 %{parent_location} 。" ensure_root_folder_permissions: - not_found: "%{group_folder} wasn't found. Please check your Nextcloud Team Folder setup." + not_found: "未找到 %{group_folder}。请检查您的 Nextcloud 团队文件夹设置。" permission_not_set: 无法设置 %{group_folder} 上的权限。 hide_inactive_folders: permission_not_set: 无法设置 %{path} 上的权限。 @@ -230,7 +230,7 @@ zh-CN: storage_delete_result_3: 自动管理的项目文件夹及其中的所有文件都将被删除 dependencies: nextcloud: - group_folders_app: Team Folders + group_folders_app: 团队文件夹 integration_app: 集成 OpenProject enabled_in_projects: setup_incomplete_description: 这个存储设置不完整。请在多个项目中启用它之前完成设置。 @@ -277,11 +277,11 @@ zh-CN: client_folder_creation: 自动创建文件夹 client_folder_removal: 自动删除文件夹 drive_contents: 驱动器内容 - files_request: Fetching team folder files + files_request: 正在获取团队文件夹文件 header: 自动托管的项目文件夹 - team_folder_app: 'Dependency: Team Folders' - team_folder_contents: Team folder content - team_folder_presence: Team folder exists + team_folder_app: '依赖项:团队文件夹' + team_folder_contents: 团队文件夹内容 + team_folder_presence: 团队文件夹已存在 userless_access: 服务器端请求身份验证 authentication: existing_token: 用户 token @@ -320,8 +320,8 @@ zh-CN: nc_oauth_request_not_found: 未找到获取当前连接用户的端点。请检查服务器日志以获取更多信息。 nc_oauth_request_unauthorized: 当前用户无权访问远程文件存储。请检查服务器日志以获取更多信息。 nc_oauth_token_missing: OpenProject 无法测试用户与 Nextcloud 之间的通信,因为用户尚未链接他们的 Nextcloud 帐户。 - nc_team_folder_not_found: The team folder could not be found. - nc_unexpected_content: Unexpected content found in the managed team folder. + nc_team_folder_not_found: 无法找到团队文件夹。 + nc_unexpected_content: 在受管理的团队文件夹中找到非预期内容。 nc_userless_access_denied: 已配置的应用密码无效。 not_configured: 无法验证连接。请先完成配置。 od_client_cant_delete_folder: 客户端在删除文件夹时遇到问题。请检查您的存储的设置文档。 diff --git a/modules/team_planner/config/locales/crowdin/js-vi.yml b/modules/team_planner/config/locales/crowdin/js-vi.yml index da30c005a53..3fba5037503 100644 --- a/modules/team_planner/config/locales/crowdin/js-vi.yml +++ b/modules/team_planner/config/locales/crowdin/js-vi.yml @@ -3,25 +3,25 @@ vi: js: team_planner: add_existing: 'Thêm hiện có' - label_team_planner_plural: 'Các kế hoạch nhóm' + label_team_planner_plural: 'Người lập kế hoạch nhóm' add_existing_title: 'Thêm các gói công việc hiện có' - create_label: 'Lịch trình nhóm' - create_title: 'Tạo kế hoạch nhóm mới' - unsaved_title: 'Kế hoạch nhóm chưa đặt tên' - no_data: 'Thêm người được phân công để thiết lập kế hoạch nhóm của bạn.' - add_assignee: 'Người được giao' - remove_assignee: 'Xóa người được phân công' + create_label: 'Người lập kế hoạch nhóm' + create_title: 'Tạo người lập kế hoạch nhóm mới' + unsaved_title: 'Người lập kế hoạch nhóm không tên' + no_data: 'Thêm người được giao để thiết lập kế hoạch nhóm của bạn.' + add_assignee: 'Người được chuyển nhượng' + remove_assignee: 'Xóa người được giao' two_weeks: '2 tuần' one_week: '1 tuần' four_weeks: '4 tuần' eight_weeks: '8 tuần' - work_week: 'Tuần làm việc' - today: 'Hôm nay' - drag_here_to_remove: 'Kéo vào đây để xóa người được phân công và ngày bắt đầu và kết thúc.' - cannot_drag_here: 'Cannot move the work package due to permissions or editing restrictions.' - cannot_drag_to_non_working_day: 'Gói công việc này không thể bắt đầu/kết thúc vào ngày không làm việc.' + work_week: 'tuần làm việc' + today: 'hôm nay' + drag_here_to_remove: 'Kéo vào đây để xóa người được giao cũng như ngày bắt đầu và ngày kết thúc.' + cannot_drag_here: 'Không thể di chuyển gói công việc do quyền hoặc hạn chế chỉnh sửa.' + cannot_drag_to_non_working_day: 'Gói công việc này không thể bắt đầu/kết thúc vào một ngày không làm việc.' quick_add: - empty_state: 'Sử dụng trường tìm kiếm để tìm các gói công việc và kéo chúng vào kế hoạch để phân công cho ai đó và xác định ngày bắt đầu và kết thúc.' + empty_state: 'Sử dụng trường tìm kiếm để tìm các gói công việc và kéo chúng vào bảng kế hoạch để giao cho ai đó cũng như xác định ngày bắt đầu và ngày kết thúc.' search_placeholder: 'Tìm kiếm...' modify: errors: diff --git a/modules/team_planner/config/locales/crowdin/vi.yml b/modules/team_planner/config/locales/crowdin/vi.yml index 47fb8fbb38b..cd72fbf034e 100644 --- a/modules/team_planner/config/locales/crowdin/vi.yml +++ b/modules/team_planner/config/locales/crowdin/vi.yml @@ -1,21 +1,21 @@ #English strings go here vi: plugin_openproject_team_planner: - name: "Lập kế hoạch nhóm OpenProject" - description: "Cung cấp các chế độ xem lập kế hoạch nhóm." + name: "Công cụ lập kế hoạch nhóm OpenProject" + description: "Cung cấp chế độ xem người lập kế hoạch nhóm." permission_view_team_planner: "Xem kế hoạch nhóm" permission_manage_team_planner: "Quản lý kế hoạch nhóm" - project_module_team_planner_view: "Các kế hoạch nhóm" + project_module_team_planner_view: "Người lập kế hoạch nhóm" ee: feature_names: - team_planner_view: "Lịch trình nhóm" + team_planner_view: "Người lập kế hoạch nhóm" upsell: team_planner_view: - title: "Lịch trình nhóm" - description: "Nhận cái nhìn tổng quan về kế hoạch của nhóm bạn với Kế hoạch Nhóm. Kéo dài, rút ngắn và kéo-thả các gói công việc để thay đổi ngày, di chuyển chúng hoặc thay đổi người được phân công." + title: "Người lập kế hoạch nhóm" + description: "Có được cái nhìn tổng quan đầy đủ về việc lập kế hoạch của nhóm bạn với Team Planner. Kéo dài, rút ​​ngắn và kéo và thả các gói công việc để sửa đổi ngày tháng, di chuyển chúng hoặc thay đổi người được giao." team_planner: - label_team_planner: "Lịch trình nhóm" - label_new_team_planner: "Kế hoạch nhóm mới" - label_create_new_team_planner: "Tạo kế hoạch nhóm mới" - label_team_planner_plural: "Kế hoạch nhóm" - label_assignees: "Người được phân công" + label_team_planner: "Người lập kế hoạch nhóm" + label_new_team_planner: "Người lập kế hoạch nhóm mới" + label_create_new_team_planner: "Tạo người lập kế hoạch nhóm mới" + label_team_planner_plural: "Người lập kế hoạch nhóm" + label_assignees: "người được chuyển nhượng" diff --git a/modules/two_factor_authentication/config/locales/crowdin/js-vi.yml b/modules/two_factor_authentication/config/locales/crowdin/js-vi.yml index f72dfc9645f..793f9f2d8dd 100644 --- a/modules/two_factor_authentication/config/locales/crowdin/js-vi.yml +++ b/modules/two_factor_authentication/config/locales/crowdin/js-vi.yml @@ -23,4 +23,4 @@ vi: js: two_factor_authentication: errors: - aborted: "Quá trình xác thực đã bị hủy. Vui lòng thử lại." + aborted: "Việc xác thực đã bị hủy. Vui lòng thử lại." diff --git a/modules/two_factor_authentication/config/locales/crowdin/vi.yml b/modules/two_factor_authentication/config/locales/crowdin/vi.yml index 072f56fefe0..b7b9c3071cf 100644 --- a/modules/two_factor_authentication/config/locales/crowdin/vi.yml +++ b/modules/two_factor_authentication/config/locales/crowdin/vi.yml @@ -3,18 +3,18 @@ vi: plugin_openproject_two_factor_authentication: name: "Xác thực hai yếu tố OpenProject" description: >- - Plugin OpenProject này xác thực người dùng của bạn bằng cách sử dụng xác thực hai yếu tố thông qua mật khẩu một lần theo tiêu chuẩn TOTP (Google Authenticator) hoặc gửi đến điện thoại di động của người dùng qua SMS hoặc cuộc gọi thoại. + Plugin OpenProject này xác thực người dùng của bạn bằng xác thực hai yếu tố bằng mật khẩu một lần thông qua tiêu chuẩn TOTP (Google Authenticator) hoặc gửi đến điện thoại di động của người dùng qua SMS hoặc cuộc gọi thoại. activerecord: attributes: two_factor_authentication/device: - identifier: "Định danh" + identifier: "định danh" default: "Giữ nguyên mặc định" - channel: "Kênh" + channel: "kênh" two_factor_authentication/device/sms: phone_number: "Số điện thoại" two_factor_authentication/device/webauthn: - webauthn_external: "WebAuthn ID" - webauthn_public_key: "WebAuthn public key" + webauthn_external: "ID WebAuthn" + webauthn_public_key: "Khóa công khai WebAuthn" errors: models: two_factor_authentication/device: @@ -69,122 +69,122 @@ vi: Hãy đặt số ngày ghi nhớ mã F2Alớn hơn 0 để cho phép người sử dụng nhớ mã 2FA của họ. Sẽ không yêu cầu nhập mã trong thời giannày. Chỉ có thể đặt nếu không bị thiết lập bắt buộc trong cấu hình. error_invalid_settings: "Phương án mã 2FA bạn lựa chon không hợp lệ" - failed_to_save_settings: "Cập nhật cài đặt 2FA thất bại: %{message}" + failed_to_save_settings: "Không cập nhật được cài đặt 2FA: %{message}" admin: - self_edit_path: "Để thêm hoặc chỉnh sửa các thiết bị 2FA của riêng bạn, vui lòng truy cập %{self_edit_link}" + self_edit_path: "Để thêm hoặc sửa đổi thiết bị 2FA của riêng bạn, vui lòng truy cập %{self_edit_link}" self_edit_link_name: "Xác thực hai yếu tố trên trang tài khoản của bạn" - self_edit_forbidden: "Bạn không thể chỉnh sửa các thiết bị 2FA của riêng bạn trên đường dẫn này. Thay vào đó, hãy truy cập My Account > Xác thực hai yếu tố." - no_devices_for_user: "Không có thiết bị 2FA nào đã được đăng ký cho người dùng này." + self_edit_forbidden: "Bạn không được chỉnh sửa thiết bị 2FA của riêng mình trên đường dẫn này. Thay vào đó hãy chuyển đến Tài khoản của tôi > Xác thực hai yếu tố." + no_devices_for_user: "Không có thiết bị 2FA nào được đăng ký cho người dùng này." all_devices_deleted: "Tất cả các thiết bị 2FA của người dùng này đã bị xóa" - delete_all_are_you_sure: "Bạn có chắc chắn muốn xóa tất cả các thiết bị 2FA cho người dùng này không?" - button_delete_all_devices: "Xóa tất cả các thiết bị 2FA đã đăng ký" + delete_all_are_you_sure: "Bạn có chắc chắn muốn xóa tất cả thiết bị 2FA cho người dùng này không?" + button_delete_all_devices: "Xóa thiết bị 2FA đã đăng ký" button_register_mobile_phone_for_user: "Đăng ký điện thoại di động" - text_2fa_enabled: "Mỗi lần đăng nhập, người dùng này sẽ được yêu cầu nhập mã OTP từ thiết bị 2FA mặc định của họ." - text_2fa_disabled: "Người dùng chưa thiết lập thiết bị 2FA thông qua 'Trang tài khoản của họ'" - only_sms_allowed: "Chỉ có chuyển tin SMS có thể được thiết lập cho các người dùng khác." + text_2fa_enabled: "Sau mỗi lần đăng nhập, người dùng này sẽ được yêu cầu nhập mã thông báo OTP từ thiết bị 2FA mặc định của họ." + text_2fa_disabled: "Người dùng chưa thiết lập thiết bị 2FA thông qua 'Trang tài khoản của tôi'" + only_sms_allowed: "Chỉ có thể thiết lập gửi SMS cho những người dùng khác." upsell: title: "Xác thực hai yếu tố" - description: "Tăng cường bảo mật cho phiên bản OpenProject của bạn bằng cách cung cấp (hoặc áp dụng) xác thực hai yếu tố cho tất cả các thành viên dự án." + description: "Tăng cường tính bảo mật cho phiên bản OpenProject của bạn bằng cách cung cấp (hoặc thực thi) xác thực hai yếu tố cho tất cả thành viên dự án." backup_codes: - none_found: Không có mã dự phòng nào cho tài khoản này. + none_found: Không có mã dự phòng nào tồn tại cho tài khoản này. singular: Mã sao lưu - plural: Các mã dự phòng + plural: Mã dự phòng your_codes: cho tài khoản %{app_name} của bạn %{login} overview_description: | - Nếu bạn không thể truy cập các thiết bị hai yếu tố của mình, bạn có thể sử dụng mã dự phòng để khôi phục quyền truy cập vào tài khoản của bạn. - Sử dụng nút dưới đây để tạo một bộ mã dự phòng mới. + Nếu bạn không thể truy cập vào thiết bị hai yếu tố của mình, bạn có thể sử dụng mã dự phòng để lấy lại quyền truy cập vào tài khoản của mình. + Sử dụng nút sau để tạo bộ mã dự phòng mới. generate: title: Tạo mã dự phòng - keep_safe_as_password: "Quan trọng! Đối xử với các mã này như mật khẩu." - keep_safe_warning: "Hãy lưu chúng trong trình quản lý mật khẩu của bạn hoặc in trang này và để ở nơi an toàn." - regenerate_warning: "Cảnh báo: Nếu bạn đã tạo mã dự phòng trước đó, chúng sẽ bị vô hiệu hóa và không còn hoạt động." + keep_safe_as_password: "Quan trọng! Hãy coi những mã này như mật khẩu." + keep_safe_warning: "Hãy lưu chúng vào trình quản lý mật khẩu của bạn hoặc in trang này và đặt ở nơi an toàn." + regenerate_warning: "Cảnh báo: Nếu bạn đã tạo mã dự phòng trước đó, chúng sẽ bị vô hiệu và không còn tác dụng." devices: add_new: "Thêm thiết bị 2FA mới" register: "Đăng ký thiết bị" confirm_default: "Xác nhận thay đổi thiết bị mặc định" confirm_device: "Xác nhận thiết bị" - confirm_now: "Chưa được xác nhận, nhấp vào đây để kích hoạt" + confirm_now: "Chưa được xác nhận, bấm vào đây để kích hoạt" cannot_delete_default: "Không thể xóa thiết bị mặc định" - make_default_are_you_sure: "Bạn có chắc chắn muốn đặt thiết bị 2FA này làm mặc định không?" - make_default_failed: "Cập nhật thiết bị 2FA mặc định thất bại." + make_default_are_you_sure: "Bạn có chắc chắn muốn đặt thiết bị 2FA này làm thiết bị mặc định không?" + make_default_failed: "Không thể cập nhật thiết bị 2FA mặc định." deletion_are_you_sure: "Bạn có chắc chắn muốn xóa thiết bị 2FA này không?" - registration_complete: "Hoàn tất đăng ký thiết bị 2FA!" - registration_failed_token_invalid: "Đăng ký thiết bị 2FA thất bại, mã xác thực không hợp lệ." - registration_failed_update: "Đăng ký thiết bị 2FA thất bại, mã xác thực hợp lệ nhưng thiết bị không thể được cập nhật." - confirm_send_failed: "Xác nhận thiết bị 2FA của bạn thất bại." + registration_complete: "Đăng ký thiết bị 2FA đã hoàn tất!" + registration_failed_token_invalid: "Đăng ký thiết bị 2FA không thành công, mã thông báo không hợp lệ." + registration_failed_update: "Đăng ký thiết bị 2FA không thành công, mã thông báo hợp lệ nhưng không thể cập nhật thiết bị." + confirm_send_failed: "Xác nhận thiết bị 2FA của bạn không thành công." button_complete_registration: "Hoàn tất đăng ký 2FA" - text_confirm_to_complete_html: "Vui lòng hoàn tất việc đăng ký thiết bị của bạn %{identifier} bằng cách nhập mật khẩu một lần từ thiết bị mặc định của bạn." - text_confirm_to_change_default_html: "Vui lòng xác nhận việc thay đổi thiết bị mặc định của bạn thành %{new_identifier} bằng cách nhập mật khẩu một lần từ thiết bị mặc định hiện tại của bạn." - text_identifier: "Bạn có thể đặt tên tùy chỉnh cho thiết bị này bằng cách sử dụng trường này." - failed_to_delete: "Xóa thiết bị 2FA thất bại." - is_default_cannot_delete: "Thiết bị này được đánh dấu là mặc định và không thể bị xóa do chính sách bảo mật đang hoạt động. Đặt thiết bị khác làm mặc định trước khi xóa." - not_existing: "Chưa có thiết bị 2FA nào được đăng ký cho tài khoản của bạn." - 2fa_from_input: Vui lòng nhập mã từ %{device_name} của bạn để xác minh danh tính của bạn. + text_confirm_to_complete_html: "Vui lòng hoàn tất quá trình đăng ký thiết bị của bạn %{identifier} bằng cách nhập mật khẩu một lần từ thiết bị mặc định của bạn." + text_confirm_to_change_default_html: "Vui lòng xác nhận việc thay đổi thiết bị mặc định của bạn thành _%{new_identifier} bằng cách nhập mật khẩu một lần từ thiết bị mặc định hiện tại của bạn." + text_identifier: "Bạn có thể cung cấp cho thiết bị một mã định danh tùy chỉnh bằng cách sử dụng trường này." + failed_to_delete: "Không thể xóa thiết bị 2FA." + is_default_cannot_delete: "Thiết bị được đánh dấu là mặc định và không thể xóa do chính sách bảo mật đang hoạt động. Đánh dấu thiết bị khác làm mặc định trước khi xóa." + not_existing: "Không có thiết bị 2FA nào được đăng ký cho tài khoản của bạn." + 2fa_from_input: Vui lòng nhập mã từ %{device_name} để xác minh danh tính của bạn. 2fa_from_webauthn: Vui lòng cung cấp thiết bị WebAuthn %{device_name}. Nếu thiết bị dựa trên USB, hãy đảm bảo cắm vào và chạm vào nó. Sau đó nhấp vào nút đăng nhập. webauthn: title: "WebAuthn" - description: Register a FIDO2 device (like YubiKey) or the secure enclave of your mobile device. - further_steps: Sau khi bạn chọn tên, bạn có thể nhấp vào nút Tiếp tục. Trình duyệt của bạn sẽ yêu cầu bạn xuất trình thiết bị WebAuthn của bạn. Khi bạn đã làm như vậy, bạn đã hoàn tất việc đăng ký thiết bị. + description: Đăng ký thiết bị FIDO2 (như YubiKey) hoặc vùng bảo mật cho thiết bị di động của bạn. + further_steps: Sau khi chọn tên, bạn có thể nhấp vào nút Tiếp tục. Trình duyệt của bạn sẽ nhắc bạn xuất trình thiết bị WebAuthn của mình. Khi bạn đã làm như vậy, bạn đã hoàn tất việc đăng ký thiết bị. totp: - title: "Xác thực dựa trên ứng dụng" - provisioning_uri: "URI cấp phát" + title: "Trình xác thực dựa trên ứng dụng" + provisioning_uri: "URI cấp phép" secret_key: "Khóa bí mật" time_based: "Dựa trên thời gian" - account: "Tên tài khoản / Nhà phát hành" + account: "Tên tài khoản/Nhà phát hành" setup: | - Để thiết lập xác thực hai yếu tố với Google Authenticator, hãy tải ứng dụng từ Apple App store hoặc Google Play Store. - Sau khi mở ứng dụng, bạn có thể quét mã QR dưới đây để đăng ký thiết bị. + Để thiết lập xác thực hai yếu tố với Google Authenticator, hãy tải xuống ứng dụng từ Apple App store hoặc Google Play Store. + Sau khi mở ứng dụng, bạn có thể quét mã QR sau để đăng ký thiết bị. question_cannot_scan: | Không thể quét mã bằng ứng dụng của bạn? text_cannot_scan: | - Nếu bạn không thể quét mã, bạn có thể nhập thông tin theo cách thủ công bằng các chi tiết sau: + Nếu không thể quét mã, bạn có thể nhập mục nhập theo cách thủ công bằng cách sử dụng các chi tiết sau: description: | Sử dụng mã một lần được tạo bởi trình xác thực như Authy hoặc Google Authenticator. sms: title: "Thiết bị di động" redacted_identifier: "Thiết bị di động (%{redacted_number})" - request_2fa_identifier: "%{redacted_identifier}, chúng tôi đã gửi cho bạn một mã xác thực qua %{delivery_channel}" + request_2fa_identifier: "%{redacted_identifier}, chúng tôi đã gửi cho bạn mã xác thực qua %{delivery_channel}" description: | - Nhận mã 2FA qua tin nhắn văn bản trên điện thoại mỗi khi bạn đăng nhập. + Nhận mã 2FA qua tin nhắn văn bản trên điện thoại của bạn mỗi khi bạn đăng nhập. sns: - delivery_failed: "Chuyển tin SNS thất bại:" + delivery_failed: "Gửi SNS không thành công:" message_bird: - sms_delivery_failed: "Chuyển tin SMS MessageBird thất bại." - voice_delivery_failed: "Cuộc gọi thoại MessageBird thất bại." + sms_delivery_failed: "Gửi SMS MessageBird không thành công." + voice_delivery_failed: "Cuộc gọi thoại MessageBird không thành công." strategies: totp: "Ứng dụng xác thực" sns: "Amazon SNS" - resdt: "API SMS Rest" + resdt: "API phần còn lại của SMS" webauthn: "WebAuthn" - mobile_transmit_notification: "Một mật khẩu một lần đã được gửi đến điện thoại di động của bạn." + mobile_transmit_notification: "Mật khẩu một lần đã được gửi đến điện thoại di động của bạn." label_two_factor_authentication: "Xác thực hai yếu tố" forced_registration: - required_to_add_device: "Một chính sách bảo mật đang hoạt động yêu cầu bạn phải kích hoạt xác thực hai yếu tố. Vui lòng sử dụng mẫu dưới đây để đăng ký một thiết bị." + required_to_add_device: "Chính sách bảo mật đang hoạt động yêu cầu bạn kích hoạt xác thực hai yếu tố. Vui lòng sử dụng mẫu sau để đăng ký thiết bị." remember: active_session_notice: > - Tài khoản của bạn có một cookie nhớ hoạt động đến %{expires_on}. Cookie này cho phép bạn đăng nhập mà không cần yếu tố thứ hai cho đến thời điểm đó. - other_active_session_notice: Tài khoản của bạn có một cookie nhớ đang hoạt động trên một phiên khác. - label: "Nhớ" + Tài khoản của bạn có cookie ghi nhớ đang hoạt động có hiệu lực cho đến %{expires_on}. Cookie này cho phép bạn đăng nhập mà không cần yếu tố thứ hai vào tài khoản của mình cho đến thời điểm đó. + other_active_session_notice: Tài khoản của bạn có cookie ghi nhớ đang hoạt động trong một phiên khác. + label: "ghi nhớ" clear_cookie: "Nhấp vào đây để xóa tất cả các phiên 2FA đã nhớ." - cookie_removed: "Tất cả các phiên 2FA đã nhớ đã bị xóa." - dont_ask_again: "Tạo cookie để nhớ xác thực 2FA trên máy khách này trong %{days} ngày." + cookie_removed: "Tất cả các phiên 2FA được ghi nhớ đã bị xóa." + dont_ask_again: "Tạo cookie để ghi nhớ xác thực 2FA trên ứng dụng khách này trong %{days} ngày." field_phone: "Điện thoại di động" field_otp: "Mật khẩu sử dụng một lần" notice_account_otp_invalid: "Mật khẩu một lần không hợp lệ." notice_account_otp_expired: "Mật khẩu một lần bạn nhập đã hết hạn." - notice_developer_strategy_otp: "Chiến lược phát triển đã tạo mã mật khẩu một lần sau: %{token} (Kênh: %{channel})" - notice_account_otp_send_failed: "Mật khẩu một lần của bạn không thể được gửi." - notice_account_has_no_phone: "Không có số điện thoại di động nào liên kết với tài khoản của bạn." + notice_developer_strategy_otp: "Chiến lược của nhà phát triển đã tạo mật khẩu một lần sau: %{token} (Kênh: %{channel})" + notice_account_otp_send_failed: "Không thể gửi mật khẩu một lần của bạn." + notice_account_has_no_phone: "Không có số điện thoại di động nào được liên kết với tài khoản của bạn." label_expiration_hint: "%{date} hoặc khi đăng xuất" - label_actions: "Hành động" - label_confirmed: "Đã xác nhận" + label_actions: "hành động" + label_confirmed: "xác nhận" button_continue: "Tiếp tục" button_make_default: "Đánh dấu là mặc định" - label_unverified_phone: "Điện thoại di động chưa được xác nhận" - notice_phone_number_format: "Vui lòng nhập số theo định dạng sau: +XX XXXXXXXX." + label_unverified_phone: "Điện thoại di động chưa được xác minh" + notice_phone_number_format: "Vui lòng nhập số theo định dạng sau: +XX XXXXXXXXX." text_otp_not_receive: "Các phương pháp xác minh khác" - text_send_otp_again: "Gửi lại mật khẩu một lần bằng:" + text_send_otp_again: "Gửi lại mật khẩu một lần bằng cách:" button_resend_otp_form: "Gửi lại" button_otp_by_voice: "Cuộc gọi thoại" - button_otp_by_sms: "SMS" - label_otp_channel: "Kênh chuyển tin" + button_otp_by_sms: "tin nhắn SMS" + label_otp_channel: "Kênh phân phối" diff --git a/modules/webhooks/config/locales/crowdin/vi.yml b/modules/webhooks/config/locales/crowdin/vi.yml index 5eb053c70ed..e2a85cfa97c 100644 --- a/modules/webhooks/config/locales/crowdin/vi.yml +++ b/modules/webhooks/config/locales/crowdin/vi.yml @@ -1,72 +1,72 @@ vi: plugin_openproject_webhooks: - name: "OpenProject Webhooks" - description: "Cung cấp API plug-in để hỗ trợ các webhook của OpenProject cho tích hợp bên thứ ba tốt hơn." + name: "Webhooks dự án mở" + description: "Cung cấp API plug-in để hỗ trợ webhooks OpenProject để tích hợp bên thứ 3 tốt hơn." activerecord: attributes: webhooks/webhook: - url: 'URL Payload' + url: 'URL tải trọng' secret: 'Chữ ký bí mật' events: 'Sự kiện' - enabled: 'Đã kích hoạt' - projects: 'Các dự án được kích hoạt' + enabled: 'đã bật' + projects: 'Dự án đã kích hoạt' webhooks/log: event_name: 'Tên sự kiện' - url: 'URL Payload' + url: 'URL tải trọng' response_code: 'Thông điệp phản hồi' response_body: 'Phản hồi' models: - webhooks/outgoing_webhook: "Webhook ra" + webhooks/outgoing_webhook: "Webhook gửi đi" webhooks: - singular: Webhook - plural: Webhooks + singular: webhook + plural: Webhook resources: time_entry: name: "Thời gian nhập" outgoing: - no_results_table: Chưa có webhook nào được định nghĩa. + no_results_table: Chưa có webhook nào được xác định. label_add_new: Thêm webhook mới label_edit: Chỉnh sửa webhook label_x_events: - one: "1 event" - other: "%{count} events" - zero: "No events" + one: "1 sự kiện" + other: "%{count} sự kiện" + zero: "Không có sự kiện" events: - created: "Đã tạo" - updated: "Đã cập nhật" - comment: "Nhận xét" - internal_comment: "ghi chú nội bộ" + created: "đã tạo" + updated: "đã cập nhật" + comment: "bình luận" + internal_comment: "Bình luận nội bộ" explanation: text: > - Khi xảy ra một sự kiện như tạo gói công việc hoặc cập nhật dự án, OpenProject sẽ gửi một yêu cầu POST đến các điểm cuối web đã cấu hình. Thông thường, sự kiện được gửi sau khi %{link} đã trôi qua. - link: khoảng thời gian tổng hợp đã cấu hình + Khi xảy ra một sự kiện như tạo gói công việc hoặc cập nhật dự án, OpenProject sẽ gửi yêu cầu POST đến các điểm cuối web đã định cấu hình. Thông thường, sự kiện được gửi sau khi %{link} trôi qua. + link: khoảng thời gian tổng hợp được cấu hình status: - enabled: 'Webhook đã được kích hoạt' - disabled: 'Webhook đã bị vô hiệu hóa' - enabled_text: 'Webhook sẽ phát ra dữ liệu cho các sự kiện đã định nghĩa dưới đây.' + enabled: 'Webhook đã được bật' + disabled: 'Webhook bị vô hiệu hóa' + enabled_text: 'Webhook sẽ phát ra tải trọng cho các sự kiện được xác định bên dưới.' disabled_text: 'Nhấp vào nút chỉnh sửa để kích hoạt webhook.' deliveries: - no_results_table: Không có lần gửi nào được thực hiện cho webhook này trong những ngày qua. - title: 'Các lần gửi gần đây' - time: 'Thời gian gửi' + no_results_table: Không có đợt giao hàng nào được thực hiện cho webhook này trong những ngày qua. + title: 'Giao hàng gần đây' + time: 'thời gian giao hàng' form: introduction: > - Gửi một yêu cầu POST đến URL payload bên dưới cho bất kỳ sự kiện nào trong dự án mà bạn đã đăng ký. Payload sẽ tương ứng với đại diện APIv3 của đối tượng đang được sửa đổi. - apiv3_doc_url: Để biết thêm thông tin, vui lòng truy cập tài liệu API + Gửi yêu cầu POST tới URL tải trọng bên dưới cho bất kỳ sự kiện nào trong dự án mà bạn đã đăng ký. Tải trọng sẽ tương ứng với biểu diễn APIv3 của đối tượng đang được sửa đổi. + apiv3_doc_url: Để biết thêm thông tin, hãy truy cập tài liệu API description: placeholder: 'Mô tả tùy chọn cho webhook.' enabled: description: > - Khi được chọn, webhook sẽ kích hoạt cho các sự kiện đã chọn. Bỏ chọn để vô hiệu hóa webhook. + Khi được chọn, webhook sẽ kích hoạt các sự kiện đã chọn. Bỏ chọn để tắt webhook. events: - title: 'Các sự kiện được kích hoạt' + title: 'Sự kiện đã bật' project_ids: - title: 'Dự án được kích hoạt' - description: 'Chọn dự án mà webhook này sẽ được thực thi.' + title: 'Dự án đã kích hoạt' + description: 'Chọn những dự án mà webhook này sẽ được thực thi.' all: 'Tất cả dự án' - selected: 'Chỉ các dự án đã chọn' + selected: 'Chỉ các dự án được chọn' selected_project_ids: - title: 'Các dự án đã chọn' + title: 'Dự án đã chọn' secret: description: > - Nếu được đặt, giá trị bí mật này sẽ được OpenProject sử dụng để ký dữ liệu webhook. + Nếu được đặt, giá trị bí mật này sẽ được OpenProject sử dụng để ký tải trọng webhook. diff --git a/modules/xls_export/config/locales/crowdin/vi.yml b/modules/xls_export/config/locales/crowdin/vi.yml index 5fb8192187e..c9359f5c543 100644 --- a/modules/xls_export/config/locales/crowdin/vi.yml +++ b/modules/xls_export/config/locales/crowdin/vi.yml @@ -1,16 +1,16 @@ vi: plugin_openproject_xls_export: - name: "Xuất XLS OpenProject" + name: "Xuất khẩu OpenProject XLS" description: "Xuất danh sách vấn đề dưới dạng bảng tính Excel (.xls)." export_to_excel: "Xuất sang XLS" - print_with_description: "Xem trước in với mô tả" + print_with_description: "Xem trước bản in với mô tả" sentence_separator_or: "hoặc" different_formats: Định dạng khác export: format: xls: "XLS" xls_with_descriptions: "XLS có mô tả" - xls_with_relations: "XLS với các mối quan hệ" + xls_with_relations: "XLS có quan hệ" xls_export: child_of: con của - parent_of: cha của + parent_of: cha mẹ của From 0028bb701f0805bbee4a9e69c9e7c8ea282d2768 Mon Sep 17 00:00:00 2001 From: Christophe Bliard Date: Fri, 6 Feb 2026 09:11:29 +0100 Subject: [PATCH 148/293] Increase log output size to 300 lines in docker workflow There was a stacktrace in a run, and we could not read the start of the stacktrace indicating which env var produced it. --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 1b568f315a4..f7f301dd62b 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -270,7 +270,7 @@ jobs: sleep 60 - docker logs openproject --tail 100 + docker logs openproject --tail 300 wget -O- --retry-on-http-error=503,502 --retry-connrefused http://localhost:8080/api/v3 - name: Push image id: push From 0b01b3beb82a42b853c9e464575afafccd0dd4b3 Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Thu, 5 Feb 2026 14:25:57 +0100 Subject: [PATCH 149/293] Bump primer 0.81.1 --- Gemfile | 2 +- Gemfile.lock | 6 +++--- frontend/package-lock.json | 28 ++++++++++++++-------------- frontend/package.json | 4 ++-- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Gemfile b/Gemfile index e1a2995e356..c021134b380 100644 --- a/Gemfile +++ b/Gemfile @@ -428,4 +428,4 @@ end gem "openproject-octicons", "~>19.32.0" gem "openproject-octicons_helper", "~>19.32.0" -gem "openproject-primer_view_components", "~>0.80.2" +gem "openproject-primer_view_components", "~>0.81.1" diff --git a/Gemfile.lock b/Gemfile.lock index 308774083ad..ecb9b41deff 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -879,7 +879,7 @@ GEM actionview openproject-octicons (= 19.32.0) railties - openproject-primer_view_components (0.80.2) + openproject-primer_view_components (0.81.1) actionview (>= 7.2.0) activesupport (>= 7.2.0) openproject-octicons (>= 19.30.1) @@ -1652,7 +1652,7 @@ DEPENDENCIES openproject-octicons (~> 19.32.0) openproject-octicons_helper (~> 19.32.0) openproject-openid_connect! - openproject-primer_view_components (~> 0.80.2) + openproject-primer_view_components (~> 0.81.1) openproject-recaptcha! openproject-reporting! openproject-storages! @@ -2028,7 +2028,7 @@ CHECKSUMS openproject-octicons (19.32.0) sha256=e9c908e7c4310d57e1dece8fc506339862f18b67b3b67d549e8f56a7b763d48b openproject-octicons_helper (19.32.0) sha256=687a8b173c6436634397477c1f05b0a575e52745a5cc1aef03351272e73e3832 openproject-openid_connect (1.0.0) - openproject-primer_view_components (0.80.2) sha256=d36d9fd48857f3dbcdd0e7a408ef9ce01211a9b09e6186ad2413b300f2e50a8e + openproject-primer_view_components (0.81.1) sha256=ebda313d71f4c7e82b9a31e0d54b1e2b5ac3776816161fb61517ced1d945587d openproject-recaptcha (1.0.0) openproject-reporting (1.0.0) openproject-storages (1.0.0) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f0bc1d3dbe8..ff9f10abdb0 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -56,12 +56,12 @@ "@ng-select/ng-select": "^20.1.0", "@ngneat/content-loader": "^7.0.0", "@openproject/octicons-angular": "^19.32.0", - "@openproject/primer-view-components": "^0.80.2", + "@openproject/primer-view-components": "^0.81.1", "@openproject/reactivestates": "^3.0.1", "@primer/css": "^22.1.0", "@primer/live-region-element": "^0.8.0", "@primer/primitives": "^11.3.2", - "@primer/view-components": "npm:@openproject/primer-view-components@^0.80.2", + "@primer/view-components": "npm:@openproject/primer-view-components@^0.81.1", "@rails/request.js": "^0.0.13", "@stimulus-components/auto-submit": "^6.0.0", "@stimulus-components/reveal": "^5.0.0", @@ -7340,9 +7340,9 @@ } }, "node_modules/@openproject/primer-view-components": { - "version": "0.80.2", - "resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.80.2.tgz", - "integrity": "sha512-ttQM5K+VRvMsxGH7HIG8QFTBw5xeIsBmvYOknQwhtNtNkj0j+fWU19osL982m/jy45evgEo0eQ/FIdEWywfrlg==", + "version": "0.81.1", + "resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.81.1.tgz", + "integrity": "sha512-4jrN87j9/T83D4oFDpAAUweXKLvi2E5Wzyh5ifZc4dK9467AWReMNcek2gIN/Ane15NIMmlei+kYjW7jrofCMw==", "license": "MIT", "dependencies": { "@github/auto-check-element": "^6.0.0", @@ -7744,9 +7744,9 @@ }, "node_modules/@primer/view-components": { "name": "@openproject/primer-view-components", - "version": "0.80.2", - "resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.80.2.tgz", - "integrity": "sha512-ttQM5K+VRvMsxGH7HIG8QFTBw5xeIsBmvYOknQwhtNtNkj0j+fWU19osL982m/jy45evgEo0eQ/FIdEWywfrlg==", + "version": "0.81.1", + "resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.81.1.tgz", + "integrity": "sha512-4jrN87j9/T83D4oFDpAAUweXKLvi2E5Wzyh5ifZc4dK9467AWReMNcek2gIN/Ane15NIMmlei+kYjW7jrofCMw==", "license": "MIT", "dependencies": { "@github/auto-check-element": "^6.0.0", @@ -30152,9 +30152,9 @@ } }, "@openproject/primer-view-components": { - "version": "0.80.2", - "resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.80.2.tgz", - "integrity": "sha512-ttQM5K+VRvMsxGH7HIG8QFTBw5xeIsBmvYOknQwhtNtNkj0j+fWU19osL982m/jy45evgEo0eQ/FIdEWywfrlg==", + "version": "0.81.1", + "resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.81.1.tgz", + "integrity": "sha512-4jrN87j9/T83D4oFDpAAUweXKLvi2E5Wzyh5ifZc4dK9467AWReMNcek2gIN/Ane15NIMmlei+kYjW7jrofCMw==", "requires": { "@github/auto-check-element": "^6.0.0", "@github/auto-complete-element": "^3.8.0", @@ -30347,9 +30347,9 @@ "integrity": "sha512-/8EDh3MmF9cbmrLETFmIuNFIdvpSCkvBlx6zzD8AZ4dZ5UYExQzFj8QAtIrRtCFJ2ZmW5QrtrPR3+JVb8KEDpg==" }, "@primer/view-components": { - "version": "npm:@openproject/primer-view-components@0.80.2", - "resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.80.2.tgz", - "integrity": "sha512-ttQM5K+VRvMsxGH7HIG8QFTBw5xeIsBmvYOknQwhtNtNkj0j+fWU19osL982m/jy45evgEo0eQ/FIdEWywfrlg==", + "version": "npm:@openproject/primer-view-components@0.81.1", + "resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.81.1.tgz", + "integrity": "sha512-4jrN87j9/T83D4oFDpAAUweXKLvi2E5Wzyh5ifZc4dK9467AWReMNcek2gIN/Ane15NIMmlei+kYjW7jrofCMw==", "requires": { "@github/auto-check-element": "^6.0.0", "@github/auto-complete-element": "^3.8.0", diff --git a/frontend/package.json b/frontend/package.json index e08178da92e..791f4b6386d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -111,12 +111,12 @@ "@ng-select/ng-select": "^20.1.0", "@ngneat/content-loader": "^7.0.0", "@openproject/octicons-angular": "^19.32.0", - "@openproject/primer-view-components": "^0.80.2", + "@openproject/primer-view-components": "^0.81.1", "@openproject/reactivestates": "^3.0.1", "@primer/css": "^22.1.0", "@primer/live-region-element": "^0.8.0", "@primer/primitives": "^11.3.2", - "@primer/view-components": "npm:@openproject/primer-view-components@^0.80.2", + "@primer/view-components": "npm:@openproject/primer-view-components@^0.81.1", "@rails/request.js": "^0.0.13", "@stimulus-components/auto-submit": "^6.0.0", "@stimulus-components/reveal": "^5.0.0", From f039bef878604908a1efa28bcedfaa72add676ae Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Fri, 6 Feb 2026 08:29:04 +0100 Subject: [PATCH 150/293] Adapt test to new ID generation on Primer checkboxes --- .../spec/features/administration/oidc_custom_crud_spec.rb | 4 ++-- spec/controllers/my_controller_spec.rb | 2 +- spec/features/admin/enterprise/enterprise_trial_spec.rb | 4 ++-- spec/features/external_link_capture_spec.rb | 6 +++--- .../projects/settings/internal_comments_settings_spec.rb | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/modules/openid_connect/spec/features/administration/oidc_custom_crud_spec.rb b/modules/openid_connect/spec/features/administration/oidc_custom_crud_spec.rb index 17b827f8fbf..27da0ec708c 100644 --- a/modules/openid_connect/spec/features/administration/oidc_custom_crud_spec.rb +++ b/modules/openid_connect/spec/features/administration/oidc_custom_crud_spec.rb @@ -79,7 +79,7 @@ RSpec.describe "OIDC administration CRUD", click_link_or_button "Continue" # Groups - enabled_checkbox = page.find_by_id("openid_connect_provider_sync_groups") + enabled_checkbox = page.find_by_id("sync_groups") expect(enabled_checkbox).not_to be_checked expect(page).to have_no_field " Groups claim" expect(page).to have_no_field "Patterns (regular expressions)" @@ -93,7 +93,7 @@ RSpec.describe "OIDC administration CRUD", click_link_or_button "Continue" # Claims - fill_in "Claims", with: '{"foo": "bar"}' + fill_in "Claims", with: '{"id_token": { "bar": null }}' fill_in "ACR values", with: "foo bar" click_link_or_button "Finish setup" diff --git a/spec/controllers/my_controller_spec.rb b/spec/controllers/my_controller_spec.rb index da4ce6deb69..e9efed08925 100644 --- a/spec/controllers/my_controller_spec.rb +++ b/spec/controllers/my_controller_spec.rb @@ -332,7 +332,7 @@ RSpec.describe MyController do render_views it "renders auto hide popups checkbox" do - expect(response.body).to have_css("form #pref_auto_hide_popups") + expect(response.body).to have_css("form #auto_hide_popups") end end diff --git a/spec/features/admin/enterprise/enterprise_trial_spec.rb b/spec/features/admin/enterprise/enterprise_trial_spec.rb index eb8b0fb3893..ea0f742b913 100644 --- a/spec/features/admin/enterprise/enterprise_trial_spec.rb +++ b/spec/features/admin/enterprise/enterprise_trial_spec.rb @@ -177,8 +177,8 @@ RSpec.describe "Enterprise trial management", fill_in "Email", with: mail retry_block do - check "enterprise_trial_general_consent", allow_label_click: true - expect(page).to have_checked_field("enterprise_trial_general_consent") + check "general_consent", allow_label_click: true + expect(page).to have_checked_field("general_consent") end end diff --git a/spec/features/external_link_capture_spec.rb b/spec/features/external_link_capture_spec.rb index 4a3d019d1a4..d54a768f973 100644 --- a/spec/features/external_link_capture_spec.rb +++ b/spec/features/external_link_capture_spec.rb @@ -71,8 +71,8 @@ RSpec.describe "External link capture", :js, :selenium do it "allows enabling external link capture and shows a confirmation screen" do visit admin_settings_external_links_path - scroll_to_element find_by_id("settings_capture_external_links") - find_by_id("settings_capture_external_links").set(true) + scroll_to_element find_by_id("capture_external_links") + find_by_id("capture_external_links").set(true) click_on "Save" expect(page).to have_text I18n.t(:notice_successful_update) @@ -106,7 +106,7 @@ RSpec.describe "External link capture", :js, :selenium do it "does not allow enabling external link capture in administration" do visit admin_settings_external_links_path - expect(page).to have_field("settings_capture_external_links", disabled: true) + expect(page).to have_field("capture_external_links", disabled: true) RequestStore.clear! expect(Setting.capture_external_links?).to be(false) diff --git a/spec/features/projects/settings/internal_comments_settings_spec.rb b/spec/features/projects/settings/internal_comments_settings_spec.rb index 5c696dd90d3..3c847e06ee1 100644 --- a/spec/features/projects/settings/internal_comments_settings_spec.rb +++ b/spec/features/projects/settings/internal_comments_settings_spec.rb @@ -40,18 +40,18 @@ RSpec.describe "WorkPackages-Settings-InternalComments", :js do internal_comments_settings_page.visit! expect(page).to have_css("#internal-comments-form") - expect(page).to have_field(:project_enabled_internal_comments, checked: false) + expect(page).to have_field(:enabled_internal_comments, checked: false) check("Enable internal comments") click_link_or_button "Save" expect_and_dismiss_flash(message: "Successful update.") - expect(page).to have_field(:project_enabled_internal_comments, checked: true) + expect(page).to have_field(:enabled_internal_comments, checked: true) uncheck("Enable internal comments") click_link_or_button "Save" expect_and_dismiss_flash(message: "Successful update.") - expect(page).to have_field(:project_enabled_internal_comments, checked: false) + expect(page).to have_field(:enabled_internal_comments, checked: false) end end From 0e9e9bb912a2266e72836d4dc4f41284677b551a Mon Sep 17 00:00:00 2001 From: ulferts Date: Fri, 6 Feb 2026 11:42:37 +0100 Subject: [PATCH 151/293] fix faulty key in schema model --- docs/api/apiv3/components/schemas/schema_property_model.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/apiv3/components/schemas/schema_property_model.yml b/docs/api/apiv3/components/schemas/schema_property_model.yml index e76ad67d7a9..ac0dedd3fcd 100644 --- a/docs/api/apiv3/components/schemas/schema_property_model.yml +++ b/docs/api/apiv3/components/schemas/schema_property_model.yml @@ -23,7 +23,7 @@ properties: writable: type: boolean description: Indicates, if the property is writable when sending a request of this schema. - object: + options: type: object description: Additional options for the property. location: From 794f9fab9d7a04552f9be899ca00cca3f8b0f778 Mon Sep 17 00:00:00 2001 From: ulferts Date: Fri, 6 Feb 2026 11:42:55 +0100 Subject: [PATCH 152/293] add placeholder property to schema model --- docs/api/apiv3/components/schemas/schema_property_model.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/api/apiv3/components/schemas/schema_property_model.yml b/docs/api/apiv3/components/schemas/schema_property_model.yml index ac0dedd3fcd..01a151eccb0 100644 --- a/docs/api/apiv3/components/schemas/schema_property_model.yml +++ b/docs/api/apiv3/components/schemas/schema_property_model.yml @@ -30,7 +30,9 @@ properties: type: string description: Defines the json path where the property is located in the payload. default: '' + placeholder: + type: string + description: A placeholder for the property to display if the property has no value. _links: type: object description: Useful links for this property (e.g. an endpoint to fetch allowed values) - From 3b3bdbdfb5f65e240b38ca98217a575d40b3ef82 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Fri, 6 Feb 2026 13:32:58 +0200 Subject: [PATCH 153/293] Add visible scope to sprints controller --- .../app/controllers/rb_sprints_controller.rb | 4 ++-- .../controllers/rb_sprints_controller_spec.rb | 21 ++++++++++++++----- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/modules/backlogs/app/controllers/rb_sprints_controller.rb b/modules/backlogs/app/controllers/rb_sprints_controller.rb index 6abdcee3e02..089a85ad4ad 100644 --- a/modules/backlogs/app/controllers/rb_sprints_controller.rb +++ b/modules/backlogs/app/controllers/rb_sprints_controller.rb @@ -94,11 +94,11 @@ class RbSprintsController < RbApplicationController # :sprint_id def load_sprint_and_project if params[:id] - @sprint = Sprint.find(params[:id]) + @sprint = Sprint.visible.find(params[:id]) @project = @sprint.project end # This overrides sprint's project if we set another project, say a subproject - @project = Project.find(params[:project_id]) if params[:project_id] + @project = Project.visible.find(params[:project_id]) if params[:project_id] end private diff --git a/modules/backlogs/spec/controllers/rb_sprints_controller_spec.rb b/modules/backlogs/spec/controllers/rb_sprints_controller_spec.rb index cb1a310ac48..99cccddba66 100644 --- a/modules/backlogs/spec/controllers/rb_sprints_controller_spec.rb +++ b/modules/backlogs/spec/controllers/rb_sprints_controller_spec.rb @@ -36,20 +36,31 @@ RSpec.describe RbSprintsController do shared_let(:user) { create(:admin) } current_user { user } + let(:visible_projects_scope) { instance_double(ActiveRecord::Relation) } + let(:visible_sprints_scope) { instance_double(ActiveRecord::Relation) } + before do allow(Setting) .to receive(:plugin_openproject_backlogs) .and_return({ "story_types" => [type_feature.id], "task_type" => type_task.id }) allow(Project) - .to receive(:find) - .with(project.identifier) - .and_return(project) + .to receive(:visible) + .and_return(visible_projects_scope) + + allow(visible_projects_scope) + .to receive(:find) + .with(project.identifier) + .and_return(project) allow(Sprint) + .to receive(:visible) + .and_return(visible_sprints_scope) + + allow(visible_sprints_scope) .to receive(:find) - .with(sprint.id.to_s) - .and_return(sprint) + .with(sprint.id.to_s) + .and_return(sprint) end describe "GET #edit_name" do From 7b71879c41592016cd63bf574f9e3c6e145b9726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Fri, 6 Feb 2026 14:29:03 +0100 Subject: [PATCH 154/293] Build hocuspocus as pre-step to docker build --- .github/workflows/docker-release.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index 6d8be7b7167..038de752ccd 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -16,6 +16,7 @@ jobs: outputs: tag: ${{ steps.compute.outputs.tag }} branch: ${{ steps.compute.outputs.branch }} + version: ${{ steps.compute.outputs.version }} steps: - name: Checkout uses: actions/checkout@v6 @@ -41,11 +42,25 @@ jobs: BRANCH_REF="dev" fi + # version strips the v prefix (v17.1.0 => 17.1.0) for hocuspocus docker tags + VERSION="${TAG_REF#v}" + echo "branch=$BRANCH_REF" | tee -a "$GITHUB_OUTPUT" echo "tag=$TAG_REF" | tee -a "$GITHUB_OUTPUT" + echo "version=$VERSION" | tee -a "$GITHUB_OUTPUT" + + build-hocuspocus: + needs: compute-inputs + uses: opf/hocuspocus/.github/workflows/docker-build.yml@dev + with: + ref: refs/tags/${{ needs.compute-inputs.outputs.tag }} + tags: | + openproject/hocuspocus:latest + openproject/hocuspocus:${{ needs.compute-inputs.outputs.version }} + secrets: inherit build: - needs: compute-inputs + needs: [compute-inputs, build-hocuspocus] uses: ./.github/workflows/docker.yml with: branch: ${{ needs.compute-inputs.outputs.branch }} From 1546de79351c4234da9574d83455b749aa30d0cd Mon Sep 17 00:00:00 2001 From: ulferts Date: Fri, 6 Feb 2026 14:38:06 +0100 Subject: [PATCH 155/293] cleanup spec structure to avoid flickering ./spec/requests/api/v3/emoji_reactions/emoji_reactions_by_activity_comment_api_spec.rb:229 # API::V3::EmojiReactions::EmojiReactionsByActivityCommentAPI PATCH /api/v3/activities/:id/emoji_reactions when user does not have permission to add work package comments fails with HTTP Forbidden flickered before --- ..._reactions_by_activity_comment_api_spec.rb | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/spec/requests/api/v3/emoji_reactions/emoji_reactions_by_activity_comment_api_spec.rb b/spec/requests/api/v3/emoji_reactions/emoji_reactions_by_activity_comment_api_spec.rb index e6535e92f60..7c9f56c2b57 100644 --- a/spec/requests/api/v3/emoji_reactions/emoji_reactions_by_activity_comment_api_spec.rb +++ b/spec/requests/api/v3/emoji_reactions/emoji_reactions_by_activity_comment_api_spec.rb @@ -34,24 +34,32 @@ require "rack/test" RSpec.describe API::V3::EmojiReactions::EmojiReactionsByActivityCommentAPI do include API::V3::Utilities::PathHelper - let(:project) { create(:project, enabled_internal_comments: true) } - let(:work_package) { create(:work_package, project:) } + shared_let(:admin) { create(:admin) } + shared_let(:project) { create(:project, enabled_internal_comments: true) } + shared_let(:work_package) do + create(:work_package, + project:, + journals: { + 1.day.ago => {}, + 1.hour.ago => { user: admin, notes: "Comment" } + }) + end let(:current_user) do create(:user, member_with_roles: { project => role }) end - let(:admin) { create(:admin) } let(:role) { create(:project_role, permissions:) } let(:permissions) do %i(view_work_packages add_work_package_comments view_internal_comments) end - let(:activity) { create(:work_package_journal, journable: work_package, user: admin, version: 2, notes: "Comment") } - let!(:emoji_reaction) { create(:emoji_reaction, reactable: activity, user: current_user) } + let(:activity) { work_package.journals.last } before do allow(User).to receive(:current).and_return(current_user) end describe "GET /api/v3/activities/:id/emoji_reactions" do + let!(:emoji_reaction) { create(:emoji_reaction, reactable: activity, user: current_user) } + shared_examples "an emoji reactions request" do before do get api_v3_paths.emoji_reactions_by_activity_comment(activity.id) @@ -108,10 +116,9 @@ RSpec.describe API::V3::EmojiReactions::EmojiReactionsByActivityCommentAPI do end context "and user does not have permission to view internal comments" do + let(:permissions) { %i(view_work_packages add_work_package_comments) } + before do - role.role_permissions - .find_by(permission: "view_internal_comments") - .destroy get api_v3_paths.emoji_reactions_by_activity_comment(internal_comment.id) end @@ -131,13 +138,8 @@ RSpec.describe API::V3::EmojiReactions::EmojiReactionsByActivityCommentAPI do patch path, { reaction: }.to_json, headers end - def destroy_all_reactions - EmojiReaction.destroy_all - end - shared_examples "a successful reaction" do before do - destroy_all_reactions make_request end @@ -174,6 +176,7 @@ RSpec.describe API::V3::EmojiReactions::EmojiReactionsByActivityCommentAPI do end context "when removing an existing reaction" do + let!(:emoji_reaction) { create(:emoji_reaction, reactable: activity, user: current_user) } let(:reaction) { emoji_reaction.reaction } before { make_request } @@ -216,7 +219,7 @@ RSpec.describe API::V3::EmojiReactions::EmojiReactionsByActivityCommentAPI do end end - context "when user does not have permission to add work package notes" do + context "when user does not have permission to add work package comments" do let(:permissions) { %i(view_work_packages) } it "fails with HTTP Forbidden" do From f74adbc405f25d71edf46e6760e054e5f1da35ae Mon Sep 17 00:00:00 2001 From: ulferts Date: Fri, 6 Feb 2026 14:49:55 +0100 Subject: [PATCH 156/293] adapt overwritten method to changes in rails 8.1 --- app/models/journable/historic_active_record_relation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/journable/historic_active_record_relation.rb b/app/models/journable/historic_active_record_relation.rb index 97ea5cbe8ef..a28902cb9a1 100644 --- a/app/models/journable/historic_active_record_relation.rb +++ b/app/models/journable/historic_active_record_relation.rb @@ -101,7 +101,7 @@ class Journable::HistoricActiveRecordRelation < ActiveRecord::Relation # # SELECT * from work_packages - def build_arel(connection, aliases = nil) + def build_arel(aliases = nil) substitute_join_tables_in_where_clause(self) # Based on the previous modifications, build the algebra object and prepend From 303447ecb812f8937a4bb84e0b8c6e4a9f203c78 Mon Sep 17 00:00:00 2001 From: ulferts Date: Fri, 6 Feb 2026 15:16:13 +0100 Subject: [PATCH 157/293] activate seemingly safe options in rails framework 8.1 --- bin/rubocop | 2 +- .../new_framework_defaults_8_1.rb | 79 +++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 config/initializers/new_framework_defaults_8_1.rb diff --git a/bin/rubocop b/bin/rubocop index 40330c0ff1c..5a20504716c 100755 --- a/bin/rubocop +++ b/bin/rubocop @@ -2,7 +2,7 @@ require "rubygems" require "bundler/setup" -# explicit rubocop config increases performance slightly while avoiding config confusion. +# Explicit RuboCop config increases performance slightly while avoiding config confusion. ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) load Gem.bin_path("rubocop", "rubocop") diff --git a/config/initializers/new_framework_defaults_8_1.rb b/config/initializers/new_framework_defaults_8_1.rb new file mode 100644 index 00000000000..ddb34d8f1fd --- /dev/null +++ b/config/initializers/new_framework_defaults_8_1.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +# Be sure to restart your server when you modify this file. +# +# This file eases your Rails 8.1 framework defaults upgrade. +# +# Uncomment each configuration one by one to switch to the new default. +# Once your application is ready to run with all new defaults, you can remove +# this file and set the `config.load_defaults` to `8.1`. +# +# Read the Guide for Upgrading Ruby on Rails for more info on each option. +# https://guides.rubyonrails.org/upgrading_ruby_on_rails.html + +### +# Skips escaping HTML entities and line separators. When set to `false`, the +# JSON renderer no longer escapes these to improve performance. +# +# Example: +# class PostsController < ApplicationController +# def index +# render json: { key: "\u2028\u2029<>&" } +# end +# end +# +# Renders `{"key":"\u2028\u2029\u003c\u003e\u0026"}` with the previous default, but `{"key":"

<>&"}` with the config +# set to `false`. +# +# Applications that want to keep the escaping behavior can set the config to `true`. +#++ +# OpenProject should not be affected by this change. At least the vast majority of JSON responses +# are rendered in the APIv3 which do not use the JSON renderer of ActionController. +# But keeping it set to true does not cost anything. +Rails.configuration.action_controller.escape_json_responses = true + +### +# Skips escaping LINE SEPARATOR (U+2028) and PARAGRAPH SEPARATOR (U+2029) in JSON. +# +# Historically these characters were not valid inside JavaScript literal strings but that changed in ECMAScript 2019. +# As such it's no longer a concern in modern browsers: https://caniuse.com/mdn-javascript_builtins_json_json_superset. +#++ +Rails.configuration.active_support.escape_js_separators_in_json = false + +### +# Raises an error when order dependent finder methods (e.g. `#first`, `#second`) are called without `order` values +# on the relation, and the model does not have any order columns (`implicit_order_column`, `query_constraints`, or +# `primary_key`) to fall back on. +# +# The current behavior of not raising an error has been deprecated, and this configuration option will be removed in +# Rails 8.2. +#++ +# Rails.configuration.active_record.raise_on_missing_required_finder_order_columns = true + +### +# Controls how Rails handles path relative URL redirects. +# When set to `:raise`, Rails will raise an `ActionController::Redirecting::UnsafeRedirectError` +# for relative URLs without a leading slash, which can help prevent open redirect vulnerabilities. +# +# Example: +# redirect_to "example.com" # Raises UnsafeRedirectError +# redirect_to "@attacker.com" # Raises UnsafeRedirectError +# redirect_to "/safe/path" # Works correctly +# +# Applications that want to allow these redirects can set the config to `:log` (previous default) +# to only log warnings, or `:notify` to send ActiveSupport notifications. +#++ +# Rails.configuration.action_controller.action_on_path_relative_redirect = :raise + +### +# Use a Ruby parser to track dependencies between Action View templates +#++ +Rails.configuration.action_view.render_tracker = :ruby + +### +# When enabled, hidden inputs generated by `form_tag`, `token_tag`, `method_tag`, and the hidden parameter fields +# included in `button_to` forms will omit the `autocomplete="off"` attribute. +# +# Applications that want to keep generating the `autocomplete` attribute for those tags can set it to `false`. +#++ +Rails.configuration.action_view.remove_hidden_field_autocomplete = true From c116e31b215d4fb0be15b6762ec471eea5af6af1 Mon Sep 17 00:00:00 2001 From: ulferts Date: Fri, 6 Feb 2026 15:17:17 +0100 Subject: [PATCH 158/293] activate raising on unsafe redirects --- config/initializers/new_framework_defaults_8_1.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/new_framework_defaults_8_1.rb b/config/initializers/new_framework_defaults_8_1.rb index ddb34d8f1fd..5988fa85158 100644 --- a/config/initializers/new_framework_defaults_8_1.rb +++ b/config/initializers/new_framework_defaults_8_1.rb @@ -63,7 +63,7 @@ Rails.configuration.active_support.escape_js_separators_in_json = false # Applications that want to allow these redirects can set the config to `:log` (previous default) # to only log warnings, or `:notify` to send ActiveSupport notifications. #++ -# Rails.configuration.action_controller.action_on_path_relative_redirect = :raise +Rails.configuration.action_controller.action_on_path_relative_redirect = :raise ### # Use a Ruby parser to track dependencies between Action View templates From 27a677e0c5f0ac812b2ec8ad2c122588125bba10 Mon Sep 17 00:00:00 2001 From: Christophe Bliard Date: Fri, 6 Feb 2026 15:47:31 +0100 Subject: [PATCH 159/293] Output Browser logs on failure for Cuprite Capybara driver --- spec/support/capybara_browser_logs.rb | 70 +++++++++++++++++++++++++-- spec/support/cuprite_setup.rb | 9 ++++ 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/spec/support/capybara_browser_logs.rb b/spec/support/capybara_browser_logs.rb index 4bb493b7210..2bede2f3843 100644 --- a/spec/support/capybara_browser_logs.rb +++ b/spec/support/capybara_browser_logs.rb @@ -4,21 +4,79 @@ module Capybara::BrowserLogs # Capture browser logs on failed examples and output them in Progress and # Documentation formatters. class Capture + # Regex matching Ferrum's incoming CDP message format: " ◀ 0.123 {json}" + CDP_INCOMING_MESSAGE_PATTERN = /^\s+◀\s+[\d.]+\s+(.+)$/ + class << self def after_failed_example(example) return unless failed?(example) return unless example.example_group.include?(Capybara::DSL) return if Capybara.page.current_url.blank? - return unless Capybara.page.driver.browser.respond_to?(:manage) - logs = Capybara.page.driver.browser.manage.instance_variable_get(:@bridge).log("browser") - example.metadata[:browser_logs] = logs + logs = extract_logs + example.metadata[:browser_logs] = logs if logs rescue StandardError => e warn "Unable to get browser logs: #{e}" end private + def extract_logs + if cuprite_driver? + extract_cuprite_logs + elsif selenium_driver? + extract_selenium_logs + end + end + + def cuprite_driver? + Capybara.page.driver.is_a?(Capybara::Cuprite::Driver) + end + + def selenium_driver? + Capybara.page.driver.browser.respond_to?(:manage) + end + + def extract_selenium_logs + Capybara.page.driver.browser.manage.instance_variable_get(:@bridge).log("browser") + end + + def extract_cuprite_logs + logger = CupriteCdpLogger.logger + return unless logger + + logger.string.each_line.filter_map do |line| + match = line.match(CDP_INCOMING_MESSAGE_PATTERN) + next unless match + + parse_console_api_called(match[1]) + end + end + + def parse_console_api_called(json_string) + data = JSON.parse(json_string) + return unless data["method"] == "Runtime.consoleAPICalled" + + params = data["params"] + type = params["type"] + args = params["args"].map { |arg| format_cdp_arg(arg) } + "#{type}: #{args.join(' ')}" + rescue JSON::ParserError + nil + end + + def format_cdp_arg(arg) + return arg["value"].to_s if arg.key?("value") + + if (preview = arg["preview"]) && (properties = preview["properties"]) + formatted = properties.map { |p| "#{p['name']}: #{p['value']}" }.join(", ") + overflow = preview["overflow"] ? ", ..." : "" + return "{#{formatted}#{overflow}}" + end + + arg["description"] || arg["type"] + end + # borrowed from capybara-screenshot code def failed?(example) return true if example.exception @@ -59,6 +117,8 @@ module Capybara::BrowserLogs logs = example.metadata[:browser_logs] .map(&:to_s) .grep_v(EXCLUDE_PATTERN) + return if logs.empty? + output.puts(" Browser logs:\n #{logs.join("\n ")}") end end @@ -68,6 +128,10 @@ end RSpec.configure do |config| config.after(type: :feature) do |example| Capybara::BrowserLogs::Capture.after_failed_example(example) + if (logger = CupriteCdpLogger.logger) + logger.truncate(0) + logger.rewind + end end config.before(:suite) do diff --git a/spec/support/cuprite_setup.rb b/spec/support/cuprite_setup.rb index 736ac329cd2..7576233d1cc 100644 --- a/spec/support/cuprite_setup.rb +++ b/spec/support/cuprite_setup.rb @@ -31,6 +31,12 @@ require "capybara/cuprite" +module CupriteCdpLogger + class << self + attr_accessor :logger + end +end + def headful_mode? ActiveRecord::Type::Boolean.new.cast(ENV.fetch("OPENPROJECT_TESTING_NO_HEADLESS", nil)) end @@ -80,6 +86,9 @@ def register_better_cuprite(language, name: :"better_cuprite_#{language}") options = configure_remote_chrome(options) + CupriteCdpLogger.logger = StringIO.new + options = options.merge(logger: CupriteCdpLogger.logger) + browser_options = { "disable-dev-shm-usage": nil, "disable-gpu": nil, From 3fb692d36bd0b60f22b47cca01f171661eb69cea Mon Sep 17 00:00:00 2001 From: ulferts Date: Fri, 6 Feb 2026 15:58:47 +0100 Subject: [PATCH 160/293] adapt specs to changes in 8.1 --- .rubocop.yml | 5 +++++ .../all_meetings/handle_ical_response_service_spec.rb | 2 +- .../subject_configuration_tab_controller_spec.rb | 4 ++-- spec/mailers/smtp_settings_spec.rb | 8 +++++--- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 9165e7eba82..17dd3fbeb3f 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -197,6 +197,11 @@ Rails/I18nLocaleAssignment: Exclude: - "spec/**/*.rb" +Rails/I18nLocaleTexts: + Enabled: true + Exclude: + - "spec/**/*.rb" + # We have config.active_record.belongs_to_required_by_default = false , # which means, we do have to declare presence validators on belongs_to relations. Rails/RedundantPresenceValidationOnBelongsTo: diff --git a/modules/meeting/spec/services/all_meetings/handle_ical_response_service_spec.rb b/modules/meeting/spec/services/all_meetings/handle_ical_response_service_spec.rb index 669f8a038a9..e68d9d41465 100644 --- a/modules/meeting/spec/services/all_meetings/handle_ical_response_service_spec.rb +++ b/modules/meeting/spec/services/all_meetings/handle_ical_response_service_spec.rb @@ -302,7 +302,7 @@ RSpec.describe AllMeetings::HandleICalResponseService, type: :model do expect(subject).to be_success expect(Rails.logger).to have_received(:warn).with( "[iCal Meeting Response] No attendee found for user #{user.mail} " \ - "in event #{recurring_meeting.uid} with recurrence ID #{recurrence_id.iso8601}" + "in event #{recurring_meeting.uid} with recurrence ID #{recurrence_id.utc.strftime('%Y-%m-%dT%H:%M:%S+00:00')}" ) end end diff --git a/spec/controllers/work_package_types/subject_configuration_tab_controller_spec.rb b/spec/controllers/work_package_types/subject_configuration_tab_controller_spec.rb index ac25c82c22e..e609c40ec8b 100644 --- a/spec/controllers/work_package_types/subject_configuration_tab_controller_spec.rb +++ b/spec/controllers/work_package_types/subject_configuration_tab_controller_spec.rb @@ -65,8 +65,8 @@ module WorkPackageTypes end context "if form data is invalid" do - let(:form_data) { { subject_configuration: "generated", pattern: nil } } - let(:expected_pattern_data) { { subject: { blueprint: "", enabled: true } } } + let(:form_data) { { subject_configuration: "generated", pattern: "{{invalid_token}}" } } + let(:expected_pattern_data) { { subject: { blueprint: "{{invalid_token}}", enabled: true } } } let(:service_result) { ServiceResult.failure } it "renders the edit template" do diff --git a/spec/mailers/smtp_settings_spec.rb b/spec/mailers/smtp_settings_spec.rb index b3d6fc62467..6cf0f6d8889 100644 --- a/spec/mailers/smtp_settings_spec.rb +++ b/spec/mailers/smtp_settings_spec.rb @@ -59,9 +59,11 @@ RSpec.describe "SMTP settings" do end def send_mail - ActionMailer::Base - .mail(from: "test@op.com", to: "foo@bar.com", subject: "Test mail", body: "body") - .deliver_now + Class.new(ActionMailer::Base) do # rubocop:disable Rails/ApplicationMailer + def test_mail + mail(from: "test@op.com", to: "foo@bar.com", subject: "Test mail", body: "body") + end + end.test_mail.deliver_now end describe "enable_starttls_auto" do From 33ce92b4e06e6774436f0100c7a477014be27a73 Mon Sep 17 00:00:00 2001 From: ulferts Date: Fri, 6 Feb 2026 16:43:16 +0100 Subject: [PATCH 161/293] activate raising on missing finder order columns --- config/initializers/new_framework_defaults_8_1.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/new_framework_defaults_8_1.rb b/config/initializers/new_framework_defaults_8_1.rb index 5988fa85158..af849867498 100644 --- a/config/initializers/new_framework_defaults_8_1.rb +++ b/config/initializers/new_framework_defaults_8_1.rb @@ -48,7 +48,7 @@ Rails.configuration.active_support.escape_js_separators_in_json = false # The current behavior of not raising an error has been deprecated, and this configuration option will be removed in # Rails 8.2. #++ -# Rails.configuration.active_record.raise_on_missing_required_finder_order_columns = true +Rails.configuration.active_record.raise_on_missing_required_finder_order_columns = true ### # Controls how Rails handles path relative URL redirects. From 7945f59458cf7cb696c6189e131a9fc2895f3871 Mon Sep 17 00:00:00 2001 From: Markus Kahl Date: Fri, 6 Feb 2026 16:31:18 +0000 Subject: [PATCH 162/293] fix hocuspocus repo name --- .github/workflows/docker-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index 038de752ccd..b73135fcd53 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -51,7 +51,7 @@ jobs: build-hocuspocus: needs: compute-inputs - uses: opf/hocuspocus/.github/workflows/docker-build.yml@dev + uses: opf/op-blocknote-hocuspocus/.github/workflows/docker-build.yml@dev with: ref: refs/tags/${{ needs.compute-inputs.outputs.tag }} tags: | From 4b93f5cee50142adf41872814ec9d52d34f4fe91 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Fri, 6 Feb 2026 15:23:33 -0300 Subject: [PATCH 163/293] Fix ambiguous display of single dates When only one of start/end date is set, show an en-dash placeholder for the missing date instead of displaying the single date alone (which looked like a milestone). Remove .compact from date_range so nils flow through to format_date_range, which now handles them explicitly. Co-Authored-By: Claude Opus 4.6 --- .../backlogs/backlog_header_component.rb | 2 +- .../backlogs/sprint_page_header_component.rb | 2 +- .../backlogs/app/helpers/rb_common_helper.rb | 7 +- .../spec/helpers/rb_common_helper_spec.rb | 75 +++++++++++++++++++ 4 files changed, 81 insertions(+), 5 deletions(-) create mode 100644 modules/backlogs/spec/helpers/rb_common_helper_spec.rb diff --git a/modules/backlogs/app/components/backlogs/backlog_header_component.rb b/modules/backlogs/app/components/backlogs/backlog_header_component.rb index 6daeacba6ac..d8c621ca19c 100644 --- a/modules/backlogs/app/components/backlogs/backlog_header_component.rb +++ b/modules/backlogs/app/components/backlogs/backlog_header_component.rb @@ -76,7 +76,7 @@ module Backlogs end def date_range - [sprint.start_date, sprint.effective_date].compact + [sprint.start_date, sprint.effective_date] end end end diff --git a/modules/backlogs/app/components/backlogs/sprint_page_header_component.rb b/modules/backlogs/app/components/backlogs/sprint_page_header_component.rb index f0b54587d94..9923f29101c 100644 --- a/modules/backlogs/app/components/backlogs/sprint_page_header_component.rb +++ b/modules/backlogs/app/components/backlogs/sprint_page_header_component.rb @@ -53,7 +53,7 @@ module Backlogs private def date_range - [@sprint.start_date, @sprint.effective_date].compact + [@sprint.start_date, @sprint.effective_date] end end end diff --git a/modules/backlogs/app/helpers/rb_common_helper.rb b/modules/backlogs/app/helpers/rb_common_helper.rb index d93c81c7398..b28a5891b70 100644 --- a/modules/backlogs/app/helpers/rb_common_helper.rb +++ b/modules/backlogs/app/helpers/rb_common_helper.rb @@ -28,9 +28,10 @@ module RbCommonHelper def format_date_range(dates) - dates - .map { |date| tag.time(datetime: date.iso8601) { format_date(date) } } - .then { |dates| safe_join(dates, " – ") } + return nil if dates.all?(&:nil?) + + from, to = dates.map { |date| tag.time(datetime: date.iso8601) { format_date(date) } if date } + safe_join([from, "–", to], " ") # – and   end def assignee_id_or_empty(story) diff --git a/modules/backlogs/spec/helpers/rb_common_helper_spec.rb b/modules/backlogs/spec/helpers/rb_common_helper_spec.rb new file mode 100644 index 00000000000..7e9b2ffe23b --- /dev/null +++ b/modules/backlogs/spec/helpers/rb_common_helper_spec.rb @@ -0,0 +1,75 @@ +# 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 RbCommonHelper do + describe "#format_date_range" do + let(:from) { Date.new(2025, 1, 6) } + let(:to) { Date.new(2025, 1, 17) } + + context "with an Array" do + it "renders both dates separated by an en-dash" do + expected = + "" \ + "\u00A0\u2013\u00A0" \ + "" + + expect(helper.format_date_range([from, to])).to be_html_eql(expected) + end + end + + context "when both dates are nil" do + it "returns nil" do + expect(helper.format_date_range([nil, nil])).to be_nil + end + end + + context "when only the start date is present" do + it "renders the start date with an en-dash" do + expected = + "" \ + "\u00A0\u2013\u00A0" + + expect(helper.format_date_range([from, nil])).to be_html_eql(expected) + end + end + + context "when only the end date is present" do + it "renders the end date with an en-dash" do + expected = + "\u00A0\u2013\u00A0" \ + "" + + expect(helper.format_date_range([nil, to])).to be_html_eql(expected) + end + end + end +end From b4970a02cf11d4cd5aa31f850d0b64332fa4283a Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Fri, 6 Feb 2026 15:30:29 -0300 Subject: [PATCH 164/293] Remove broken toggle transition --- frontend/src/assets/sass/backlogs/_master_backlog.sass | 8 -------- 1 file changed, 8 deletions(-) diff --git a/frontend/src/assets/sass/backlogs/_master_backlog.sass b/frontend/src/assets/sass/backlogs/_master_backlog.sass index 884f9d3cb97..cd43321616e 100644 --- a/frontend/src/assets/sass/backlogs/_master_backlog.sass +++ b/frontend/src/assets/sass/backlogs/_master_backlog.sass @@ -64,14 +64,6 @@ $op-backlogs-header--points-min-width-narrow: 2rem display: inline white-space: nowrap - &--toggle - svg - transition: opacity 120ms ease - transform-origin: center - - &[hidden] - opacity: 0 - .op-backlogs-story display: grid grid-template-columns: var(--control-xsmall-size) 1fr minmax($op-backlogs-header--points-min-width, max-content) auto From eba6b1b775699a48806871fd7851ef4d0c0c2a95 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Fri, 6 Feb 2026 15:31:48 -0300 Subject: [PATCH 165/293] Remove superfluous drag handle styling --- frontend/src/assets/sass/backlogs/_master_backlog.sass | 3 --- 1 file changed, 3 deletions(-) diff --git a/frontend/src/assets/sass/backlogs/_master_backlog.sass b/frontend/src/assets/sass/backlogs/_master_backlog.sass index cd43321616e..159b6dd9ae2 100644 --- a/frontend/src/assets/sass/backlogs/_master_backlog.sass +++ b/frontend/src/assets/sass/backlogs/_master_backlog.sass @@ -73,9 +73,6 @@ $op-backlogs-header--points-min-width-narrow: 2rem margin-top: calc(-1 * var(--base-size-4)) margin-bottom: var(--base-size-4) -.op-backlogs-story--drag_handle - display: flex - .op-backlogs-story--drag_handle_button padding: var(--base-size-4) From 21180208ccbef6670093740c531f7b29c0063061 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Fri, 6 Feb 2026 16:19:07 -0300 Subject: [PATCH 166/293] Show failure reason if Story cannot be updated --- modules/backlogs/app/controllers/rb_stories_controller.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/backlogs/app/controllers/rb_stories_controller.rb b/modules/backlogs/app/controllers/rb_stories_controller.rb index b0a6c3db1a6..240708202ec 100644 --- a/modules/backlogs/app/controllers/rb_stories_controller.rb +++ b/modules/backlogs/app/controllers/rb_stories_controller.rb @@ -45,7 +45,9 @@ class RbStoriesController < RbApplicationController ) unless call.success? - render_error_flash_message_via_turbo_stream(message: I18n.t(:notice_unsuccessful_update)) # TODO: display reason + render_error_flash_message_via_turbo_stream( + message: I18n.t(:notice_unsuccessful_update_with_reason, reason: call.message) + ) end backlog = Backlog.for(sprint: @sprint, project: @project) @@ -72,7 +74,9 @@ class RbStoriesController < RbApplicationController .call(attributes: { move_to: reorder_param }) unless call.success? - render_error_flash_message_via_turbo_stream(message: I18n.t(:notice_unsuccessful_update)) # TODO: display reason + render_error_flash_message_via_turbo_stream( + message: I18n.t(:notice_unsuccessful_update_with_reason, reason: call.message) + ) end backlog = Backlog.for(sprint: @sprint, project: @project) From 6dd4a22fe3644de1afb39c3a411e8f8fcfeb68de Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Fri, 6 Feb 2026 16:28:56 -0300 Subject: [PATCH 167/293] Flesh out backlogs controller specs Co-Authored-By: Claude Opus 4.6 --- .../rb_master_backlogs_controller_spec.rb | 52 +++++++++++++------ .../controllers/rb_sprints_controller_spec.rb | 12 +++++ .../controllers/rb_stories_controller_spec.rb | 52 +++++++++++++++++++ 3 files changed, 99 insertions(+), 17 deletions(-) diff --git a/modules/backlogs/spec/controllers/rb_master_backlogs_controller_spec.rb b/modules/backlogs/spec/controllers/rb_master_backlogs_controller_spec.rb index 992a47b9d59..3d115214828 100644 --- a/modules/backlogs/spec/controllers/rb_master_backlogs_controller_spec.rb +++ b/modules/backlogs/spec/controllers/rb_master_backlogs_controller_spec.rb @@ -48,22 +48,31 @@ RSpec.describe RbMasterBacklogsController do end describe "GET #index" do - it "is successful" do + it "is successful", :aggregate_failures do get :index, params: { project_id: project.id } expect(response).to be_successful - end - - it "assigns @owner_backlogs and @sprint_backlogs" do - get :index, params: { project_id: project.id } - + expect(assigns(:project)).to eq(project) expect(assigns(:owner_backlogs)).to be_an(Array) expect(assigns(:sprint_backlogs)).to be_an(Array) end + + context "with a Turbo Frame request" do + before { request.headers["Turbo-Frame"] = "backlogs_container" } + + it "renders the list partial", :aggregate_failures do + get :index, params: { project_id: project.id } + + expect(response).to be_successful + expect(assigns(:project)).to eq(project) + expect(assigns(:owner_backlogs)).to be_an(Array) + expect(assigns(:sprint_backlogs)).to be_an(Array) + end + end end describe "GET #details" do - it "is successful" do + it "is successful", :aggregate_failures do get :details, params: { project_id: project.id, tab: :overview, @@ -72,18 +81,27 @@ RSpec.describe RbMasterBacklogsController do } expect(response).to be_successful - end - - it "assigns @owner_backlogs and @sprint_backlogs" do - get :details, params: { - project_id: project.id, - tab: :overview, - work_package_id: story.id, - work_package_split_view: true - } - + expect(assigns(:project)).to eq(project) expect(assigns(:owner_backlogs)).to be_an(Array) expect(assigns(:sprint_backlogs)).to be_an(Array) end + + context "with a Turbo Frame request" do + before { request.headers["Turbo-Frame"] = "content-bodyRight" } + + it "renders the split view without loading backlogs", :aggregate_failures do + get :details, params: { + project_id: project.id, + tab: :overview, + work_package_id: story.id, + work_package_split_view: true + } + + expect(response).to be_successful + expect(assigns(:project)).to eq(project) + expect(assigns(:owner_backlogs)).to be_nil + expect(assigns(:sprint_backlogs)).to be_nil + end + end end end diff --git a/modules/backlogs/spec/controllers/rb_sprints_controller_spec.rb b/modules/backlogs/spec/controllers/rb_sprints_controller_spec.rb index 99cccddba66..5513fdaff5e 100644 --- a/modules/backlogs/spec/controllers/rb_sprints_controller_spec.rb +++ b/modules/backlogs/spec/controllers/rb_sprints_controller_spec.rb @@ -73,6 +73,9 @@ RSpec.describe RbSprintsController do expect(response).to be_successful expect(response).to have_http_status :ok expect(response).to have_turbo_stream action: "update", target: "backlogs-backlog-header-component-#{sprint.id}" + expect(assigns(:project)).to eq(project) + expect(assigns(:sprint)).to eq(sprint) + expect(assigns(:backlog)).to be_a(Backlog) end end @@ -86,6 +89,9 @@ RSpec.describe RbSprintsController do expect(response).to be_successful expect(response).to have_http_status :ok expect(response).to have_turbo_stream action: "update", target: "backlogs-backlog-header-component-#{sprint.id}" + expect(assigns(:project)).to eq(project) + expect(assigns(:sprint)).to eq(sprint) + expect(assigns(:backlog)).to be_a(Backlog) end end @@ -113,6 +119,9 @@ RSpec.describe RbSprintsController do expect(response).to have_http_status :ok expect(response).to have_turbo_stream action: "update", target: "backlogs-backlog-header-component-#{sprint.id}" expect(response).to have_turbo_stream action: "flash", target: "op-primer-flash-component" + expect(assigns(:project)).to eq(project) + expect(assigns(:sprint)).to eq(sprint) + expect(assigns(:backlog)).to be_a(Backlog) end end @@ -131,6 +140,9 @@ RSpec.describe RbSprintsController do expect(response).to have_http_status :unprocessable_entity expect(response).to have_turbo_stream action: "update", target: "backlogs-backlog-header-component-#{sprint.id}" expect(response).to have_turbo_stream action: "flash", target: "op-primer-flash-component" + expect(assigns(:project)).to eq(project) + expect(assigns(:sprint)).to eq(sprint) + expect(assigns(:backlog)).to be_a(Backlog) end end end diff --git a/modules/backlogs/spec/controllers/rb_stories_controller_spec.rb b/modules/backlogs/spec/controllers/rb_stories_controller_spec.rb index ab73f04b839..dd18b71a314 100644 --- a/modules/backlogs/spec/controllers/rb_stories_controller_spec.rb +++ b/modules/backlogs/spec/controllers/rb_stories_controller_spec.rb @@ -65,6 +65,35 @@ RSpec.describe RbStoriesController do expect(response).to have_turbo_stream action: "replace", target: "backlogs-backlog-component-#{sprint.id}" expect(response).to have_turbo_stream action: "replace", target: "backlogs-backlog-component-#{other_sprint.id}" expect(response).to have_turbo_stream action: "flash", target: "op-primer-flash-component" + expect(assigns(:project)).to eq(project) + expect(assigns(:sprint)).to eq(sprint) + end + + context "when service call fails" do + let(:service_result) { ServiceResult.failure(message: "Something went wrong") } + + before do + update_service = instance_double(Stories::UpdateService, call: service_result) + + allow(Stories::UpdateService) + .to receive(:new) + .and_return(update_service) + end + + it "renders an error flash", :aggregate_failures do + put :move, params: { + project_id: project.id, + sprint_id: sprint.id, + id: story.id, + target_id: other_sprint.id, + position: 1 + }, + format: :turbo_stream + + expect(response).to be_successful + expect(response).to have_turbo_stream action: "replace", target: "backlogs-backlog-component-#{sprint.id}" + expect(response).to have_turbo_stream action: "flash", target: "op-primer-flash-component" + end end end @@ -76,6 +105,29 @@ RSpec.describe RbStoriesController do expect(response).to be_successful expect(response).to have_http_status :ok expect(response).to have_turbo_stream action: "replace", target: "backlogs-backlog-component-#{sprint.id}" + expect(assigns(:project)).to eq(project) + expect(assigns(:sprint)).to eq(sprint) + end + + context "when service call fails" do + let(:service_result) { ServiceResult.failure(message: "Something went wrong") } + + before do + update_service = instance_double(Stories::UpdateService, call: service_result) + + allow(Stories::UpdateService) + .to receive(:new) + .and_return(update_service) + end + + it "renders an error flash", :aggregate_failures do + post :reorder, params: { project_id: project.id, sprint_id: sprint.id, id: story.id, direction: "highest" }, + format: :turbo_stream + + expect(response).to be_successful + expect(response).to have_turbo_stream action: "replace", target: "backlogs-backlog-component-#{sprint.id}" + expect(response).to have_turbo_stream action: "flash", target: "op-primer-flash-component" + end end end end From b956deb8e992dc0fab6d926440dfe458944582fa Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Fri, 6 Feb 2026 16:46:15 -0300 Subject: [PATCH 168/293] DRY up `RbSprintsController` Extract `update_header_component_via_turbo_stream` helper to remove repeated backlog-build + turbo-stream-update block from `edit_name`, `show_name`, and `update` actions. Co-Authored-By: Claude Opus 4.6 --- .../app/controllers/rb_sprints_controller.rb | 55 +++++++------------ 1 file changed, 20 insertions(+), 35 deletions(-) diff --git a/modules/backlogs/app/controllers/rb_sprints_controller.rb b/modules/backlogs/app/controllers/rb_sprints_controller.rb index 089a85ad4ad..32d1d4402ae 100644 --- a/modules/backlogs/app/controllers/rb_sprints_controller.rb +++ b/modules/backlogs/app/controllers/rb_sprints_controller.rb @@ -32,61 +32,34 @@ class RbSprintsController < RbApplicationController include OpTurbo::ComponentStream def edit_name - @backlog = Backlog.for(sprint: @sprint, project: @project) - - update_via_turbo_stream( - component: Backlogs::BacklogHeaderComponent.new( - backlog: @backlog, - project: @project, - state: :edit - ) - ) - + update_header_component_via_turbo_stream(state: :edit) respond_with_turbo_streams end def show_name - @backlog = Backlog.for(sprint: @sprint, project: @project) - - update_via_turbo_stream( - component: Backlogs::BacklogHeaderComponent.new( - backlog: @backlog, - project: @project, - state: :show - ) - ) - + update_header_component_via_turbo_stream(state: :show) respond_with_turbo_streams end def update call = Versions::UpdateService - .new(user: current_user, model: @sprint) - .call(attributes: sprint_params) + .new(user: current_user, model: @sprint) + .call(attributes: sprint_params) if call.success? status = 200 state = :show - @sprint = call.result - render_success_flash_message_via_turbo_stream(message: I18n.t(:notice_successful_update)) else status = 422 state = :edit - - render_error_flash_message_via_turbo_stream(message: I18n.t(:notice_unsuccessful_update_with_reason, reason: call.message)) + render_error_flash_message_via_turbo_stream( + message: I18n.t(:notice_unsuccessful_update_with_reason, reason: call.message) + ) end - @backlog = Backlog.for(sprint: @sprint, project: @project) - - update_via_turbo_stream( - component: Backlogs::BacklogHeaderComponent.new( - backlog: @backlog, - project: @project, - state: - ) - ) + update_header_component_via_turbo_stream(state:) respond_with_turbo_streams(status:) end @@ -103,6 +76,18 @@ class RbSprintsController < RbApplicationController private + def update_header_component_via_turbo_stream(state: :show) + @backlog = Backlog.for(sprint: @sprint, project: @project) + + update_via_turbo_stream( + component: Backlogs::BacklogHeaderComponent.new( + backlog: @backlog, + project: @project, + state: + ) + ) + end + def sprint_params params.expect(sprint: %i[name start_date effective_date]) end From 6edc43a2573998efe973b20fe182e92da3593208 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Fri, 6 Feb 2026 16:52:02 -0300 Subject: [PATCH 169/293] Fix `load_sprint_and_project` visibility Make it private (matching the parent class) and remove unnecessary guards since params are always present in the routes. Co-Authored-By: Claude Opus 4.6 --- .../app/controllers/rb_sprints_controller.rb | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/modules/backlogs/app/controllers/rb_sprints_controller.rb b/modules/backlogs/app/controllers/rb_sprints_controller.rb index 32d1d4402ae..60abfe686dc 100644 --- a/modules/backlogs/app/controllers/rb_sprints_controller.rb +++ b/modules/backlogs/app/controllers/rb_sprints_controller.rb @@ -63,17 +63,6 @@ class RbSprintsController < RbApplicationController respond_with_turbo_streams(status:) end - # Overwrite load_sprint_and_project to load the sprint from the :id instead of - # :sprint_id - def load_sprint_and_project - if params[:id] - @sprint = Sprint.visible.find(params[:id]) - @project = @sprint.project - end - # This overrides sprint's project if we set another project, say a subproject - @project = Project.visible.find(params[:project_id]) if params[:project_id] - end - private def update_header_component_via_turbo_stream(state: :show) @@ -88,6 +77,14 @@ class RbSprintsController < RbApplicationController ) end + # Overrides load_sprint_and_project to load the sprint from :id instead of :sprint_id + def load_sprint_and_project + @sprint = Sprint.visible.find(params[:id]) + @project = @sprint.project + # This overrides sprint's project if we set another project, say a subproject + @project = Project.visible.find(params[:project_id]) + end + def sprint_params params.expect(sprint: %i[name start_date effective_date]) end From 1fbd1f1d9f072036dc5866ad480bc36ec5ee2790 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Fri, 6 Feb 2026 16:57:10 -0300 Subject: [PATCH 170/293] DRY up `RbStoriesController` Extract `load_story` before_action and `replace_backlog_component_via_turbo_stream` helper to remove repeated story lookup and backlog component rendering. Co-Authored-By: Claude Opus 4.6 --- .../app/controllers/rb_stories_controller.rb | 48 ++++++++++--------- .../controllers/rb_stories_controller_spec.rb | 4 ++ 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/modules/backlogs/app/controllers/rb_stories_controller.rb b/modules/backlogs/app/controllers/rb_stories_controller.rb index 240708202ec..0b1048a2349 100644 --- a/modules/backlogs/app/controllers/rb_stories_controller.rb +++ b/modules/backlogs/app/controllers/rb_stories_controller.rb @@ -31,18 +31,19 @@ class RbStoriesController < RbApplicationController include OpTurbo::ComponentStream + before_action :load_story + def move # rubocop:disable Metrics/AbcSize - story = Story.visible.find(params[:id]) - # The #move_after called in update service required reloading the story, hence - # it is required to memoize the previous version_id. - version_id_was = story.version_id + # The update service reloads the story internally (via #move_after), + # so we memoize the previous version_id before the call. + version_id_was = @story.version_id call = Stories::UpdateService - .new(user: current_user, story:) - .call( - attributes: { version_id: move_params[:target_id] }, - position: move_params[:position].to_i - ) + .new(user: current_user, story: @story) + .call( + attributes: { version_id: move_params[:target_id] }, + position: move_params[:position].to_i + ) unless call.success? render_error_flash_message_via_turbo_stream( @@ -50,28 +51,24 @@ class RbStoriesController < RbApplicationController ) end - backlog = Backlog.for(sprint: @sprint, project: @project) - replace_via_turbo_stream(component: Backlogs::BacklogComponent.new(backlog:, project: @project)) + replace_backlog_component_via_turbo_stream(sprint: @sprint) - if story.version_id != version_id_was - new_sprint = story.version.becomes(Sprint) - new_backlog = Backlog.for(sprint: new_sprint, project: @project) + if @story.version_id != version_id_was + new_sprint = @story.version.becomes(Sprint) render_success_flash_message_via_turbo_stream( message: I18n.t(:notice_successful_move, from: @sprint.name, to: new_sprint.name) ) - replace_via_turbo_stream(component: Backlogs::BacklogComponent.new(backlog: new_backlog, project: @project)) + replace_backlog_component_via_turbo_stream(sprint: new_sprint) end respond_with_turbo_streams end def reorder - story = Story.visible.find(params[:id]) - call = Stories::UpdateService - .new(user: current_user, story:) - .call(attributes: { move_to: reorder_param }) + .new(user: current_user, story: @story) + .call(attributes: { move_to: reorder_param }) unless call.success? render_error_flash_message_via_turbo_stream( @@ -79,15 +76,22 @@ class RbStoriesController < RbApplicationController ) end - backlog = Backlog.for(sprint: @sprint, project: @project) - - replace_via_turbo_stream(component: Backlogs::BacklogComponent.new(backlog:, project: @project)) + replace_backlog_component_via_turbo_stream(sprint: @sprint) respond_with_turbo_streams end private + def replace_backlog_component_via_turbo_stream(sprint:) + @backlog = Backlog.for(sprint:, project: @project) + replace_via_turbo_stream(component: Backlogs::BacklogComponent.new(backlog: @backlog, project: @project)) + end + + def load_story + @story = Story.visible.find(params[:id]) + end + def move_params params.require(%i[position target_id]) params.permit(:position, :target_id) diff --git a/modules/backlogs/spec/controllers/rb_stories_controller_spec.rb b/modules/backlogs/spec/controllers/rb_stories_controller_spec.rb index dd18b71a314..90ce9199be1 100644 --- a/modules/backlogs/spec/controllers/rb_stories_controller_spec.rb +++ b/modules/backlogs/spec/controllers/rb_stories_controller_spec.rb @@ -67,6 +67,8 @@ RSpec.describe RbStoriesController do expect(response).to have_turbo_stream action: "flash", target: "op-primer-flash-component" expect(assigns(:project)).to eq(project) expect(assigns(:sprint)).to eq(sprint) + expect(assigns(:story)).to eq(story) + expect(assigns(:backlog)).to be_a(Backlog) end context "when service call fails" do @@ -107,6 +109,8 @@ RSpec.describe RbStoriesController do expect(response).to have_turbo_stream action: "replace", target: "backlogs-backlog-component-#{sprint.id}" expect(assigns(:project)).to eq(project) expect(assigns(:sprint)).to eq(sprint) + expect(assigns(:story)).to eq(story) + expect(assigns(:backlog)).to be_a(Backlog) end context "when service call fails" do From fc285cdbc0857e5c77274781d90a3741407dd148 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Fri, 6 Feb 2026 17:18:05 -0300 Subject: [PATCH 171/293] Fix breadcrumb and misc "unfriendly" project links We should generate friendly URLs using the project identifier, not project id. While this inconsistency is pervasive in the OpenProject codebase, this commit aims to at least make links within the backlogs module a consistent format. --- .../backlogs/backlog_menu_component.html.erb | 2 +- .../backlogs/sprint_page_header_component.rb | 2 +- .../backlogs/app/controllers/rb_wikis_controller.rb | 4 ++-- .../app/views/projects/settings/backlogs/show.html.erb | 4 ++-- .../app/views/rb_master_backlogs/index.html.erb | 2 +- .../backlogs/app/views/shared/_server_variables.js.erb | 10 +++++----- .../backlogs/app/views/shared/not_configured.html.erb | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/modules/backlogs/app/components/backlogs/backlog_menu_component.html.erb b/modules/backlogs/app/components/backlogs/backlog_menu_component.html.erb index 520958c0ed1..78f56b0dba4 100644 --- a/modules/backlogs/app/components/backlogs/backlog_menu_component.html.erb +++ b/modules/backlogs/app/components/backlogs/backlog_menu_component.html.erb @@ -107,7 +107,7 @@ See COPYRIGHT and LICENSE files for more details. menu.with_item( label: t(".action_menu.properties"), tag: :a, - href: edit_version_path(sprint, back_url: backlogs_project_backlogs_path(project), project_id: project.id) + href: edit_version_path(sprint, back_url: backlogs_project_backlogs_path(project), project_id: project) ) do |item| item.with_leading_visual_icon(icon: :gear) end diff --git a/modules/backlogs/app/components/backlogs/sprint_page_header_component.rb b/modules/backlogs/app/components/backlogs/sprint_page_header_component.rb index 9923f29101c..60158874b0c 100644 --- a/modules/backlogs/app/components/backlogs/sprint_page_header_component.rb +++ b/modules/backlogs/app/components/backlogs/sprint_page_header_component.rb @@ -45,7 +45,7 @@ module Backlogs end def breadcrumb_items - [{ href: project_overview_path(@project.id), text: @project.name }, + [{ href: project_overview_path(@project), text: @project.name }, { href: backlogs_project_backlogs_path(@project), text: t(:label_backlogs) }, @sprint.name] end diff --git a/modules/backlogs/app/controllers/rb_wikis_controller.rb b/modules/backlogs/app/controllers/rb_wikis_controller.rb index edd31eef7c1..7684363cffa 100644 --- a/modules/backlogs/app/controllers/rb_wikis_controller.rb +++ b/modules/backlogs/app/controllers/rb_wikis_controller.rb @@ -34,10 +34,10 @@ class RbWikisController < RbApplicationController # # NOTE: The methods #show and #edit create a template page when called. def show - redirect_to controller: "/wiki", action: "index", project_id: @project.id, id: @sprint.wiki_page + redirect_to controller: "/wiki", action: "index", project_id: @project, id: @sprint.wiki_page end def edit - redirect_to controller: "/wiki", action: "edit", project_id: @project.id, id: @sprint.wiki_page + redirect_to controller: "/wiki", action: "edit", project_id: @project, id: @sprint.wiki_page end end diff --git a/modules/backlogs/app/views/projects/settings/backlogs/show.html.erb b/modules/backlogs/app/views/projects/settings/backlogs/show.html.erb index 49424558d34..bb32c332dbf 100644 --- a/modules/backlogs/app/views/projects/settings/backlogs/show.html.erb +++ b/modules/backlogs/app/views/projects/settings/backlogs/show.html.erb @@ -31,8 +31,8 @@ See COPYRIGHT and LICENSE files for more details. render Primer::OpenProject::PageHeader.new do |header| header.with_title { t("backlogs.definition_of_done") } header.with_breadcrumbs( - [{ href: project_overview_path(@project.id), text: @project.name }, - { href: project_settings_general_path(@project.id), text: I18n.t(:label_project_settings) }, + [{ href: project_overview_path(@project), text: @project.name }, + { href: project_settings_general_path(@project), text: I18n.t(:label_project_settings) }, t("backlogs.definition_of_done")] ) end diff --git a/modules/backlogs/app/views/rb_master_backlogs/index.html.erb b/modules/backlogs/app/views/rb_master_backlogs/index.html.erb index 5f87d6dfd1b..ee5f07a80e8 100644 --- a/modules/backlogs/app/views/rb_master_backlogs/index.html.erb +++ b/modules/backlogs/app/views/rb_master_backlogs/index.html.erb @@ -38,7 +38,7 @@ See COPYRIGHT and LICENSE files for more details. render Primer::OpenProject::PageHeader.new do |header| header.with_title { t(:label_backlogs) } header.with_breadcrumbs( - [{ href: project_overview_path(@project.id), text: @project.name }, + [{ href: project_overview_path(@project), text: @project.name }, t(:label_backlogs)] ) end diff --git a/modules/backlogs/app/views/shared/_server_variables.js.erb b/modules/backlogs/app/views/shared/_server_variables.js.erb index 06d9390d586..2c3199bbe29 100644 --- a/modules/backlogs/app/views/shared/_server_variables.js.erb +++ b/modules/backlogs/app/views/shared/_server_variables.js.erb @@ -38,13 +38,13 @@ RB.constants = { RB.urlFor = (function () { const routes = { - update_sprint: '<%= backlogs_project_sprint_path(project_id: @project.identifier, id: ":id") %>', + update_sprint: '<%= backlogs_project_sprint_path(project_id: @project, id: ":id") %>', - create_task: '<%= backlogs_project_sprint_tasks_path(project_id: @project.identifier, sprint_id: ":sprint_id") %>', - update_task: '<%= backlogs_project_sprint_task_path(project_id: @project.identifier, sprint_id: ":sprint_id", id: ":id") %>', + create_task: '<%= backlogs_project_sprint_tasks_path(project_id: @project, sprint_id: ":sprint_id") %>', + update_task: '<%= backlogs_project_sprint_task_path(project_id: @project, sprint_id: ":sprint_id", id: ":id") %>', - create_impediment: '<%= backlogs_project_sprint_impediments_path(project_id: @project.identifier, sprint_id: ":sprint_id") %>', - update_impediment: '<%= backlogs_project_sprint_impediment_path(project_id: @project.identifier, sprint_id: ":sprint_id", id: ":id") %>' + create_impediment: '<%= backlogs_project_sprint_impediments_path(project_id: @project, sprint_id: ":sprint_id") %>', + update_impediment: '<%= backlogs_project_sprint_impediment_path(project_id: @project, sprint_id: ":sprint_id", id: ":id") %>' }; return function (routeName, options) { diff --git a/modules/backlogs/app/views/shared/not_configured.html.erb b/modules/backlogs/app/views/shared/not_configured.html.erb index c1475db3916..03689ecfd3b 100644 --- a/modules/backlogs/app/views/shared/not_configured.html.erb +++ b/modules/backlogs/app/views/shared/not_configured.html.erb @@ -34,7 +34,7 @@ See COPYRIGHT and LICENSE files for more details. render Primer::OpenProject::PageHeader.new do |header| header.with_title { t(:label_backlogs) } header.with_breadcrumbs( - [{ href: project_overview_path(@project.identifier), text: @project.name }, + [{ href: project_overview_path(@project), text: @project.name }, t(:label_backlogs)] ) end From 3bc6f32b7d8297d5ccfdb39ebeaec3cd3fc32a02 Mon Sep 17 00:00:00 2001 From: OpenProject Actions CI Date: Sat, 7 Feb 2026 03:49:27 +0000 Subject: [PATCH 172/293] update locales from crowdin [ci skip] --- config/locales/crowdin/es.yml | 174 +++++++++--------- config/locales/crowdin/it.yml | 2 +- config/locales/crowdin/ja.yml | 108 +++++------ config/locales/crowdin/js-es.yml | 6 +- config/locales/crowdin/js-ja.yml | 28 +-- config/locales/crowdin/js-pt-BR.yml | 6 +- config/locales/crowdin/js-tr.yml | 8 +- config/locales/crowdin/js-uk.yml | 2 +- config/locales/crowdin/ko.yml | 2 +- config/locales/crowdin/pl.yml | 2 +- config/locales/crowdin/pt-BR.yml | 172 ++++++++--------- config/locales/crowdin/tr.yml | 12 +- config/locales/crowdin/uk.yml | 22 +-- config/locales/crowdin/vi.yml | 8 +- config/locales/crowdin/zh-CN.yml | 2 +- config/locales/crowdin/zh-TW.yml | 4 +- .../auth_saml/config/locales/crowdin/tr.yml | 2 +- .../backlogs/config/locales/crowdin/ja.yml | 20 +- .../bim/config/locales/crowdin/ja.seeders.yml | 4 +- modules/bim/config/locales/crowdin/ja.yml | 2 +- modules/costs/config/locales/crowdin/ja.yml | 4 +- .../costs/config/locales/crowdin/js-ja.yml | 2 +- modules/costs/config/locales/crowdin/tr.yml | 2 +- .../config/locales/crowdin/es.seeders.yml | 2 +- .../documents/config/locales/crowdin/es.yml | 2 +- .../documents/config/locales/crowdin/ja.yml | 46 ++--- .../config/locales/crowdin/tr.seeders.yml | 2 +- .../documents/config/locales/crowdin/tr.yml | 6 +- .../grids/config/locales/crowdin/js-tr.yml | 4 +- modules/grids/config/locales/crowdin/tr.yml | 4 +- .../job_status/config/locales/crowdin/tr.yml | 2 +- modules/meeting/config/locales/crowdin/es.yml | 50 ++--- modules/meeting/config/locales/crowdin/ja.yml | 142 +++++++------- .../meeting/config/locales/crowdin/pt-BR.yml | 28 +-- modules/meeting/config/locales/crowdin/tr.yml | 2 +- modules/meeting/config/locales/crowdin/vi.yml | 12 +- .../meeting/config/locales/crowdin/zh-TW.yml | 12 +- .../config/locales/crowdin/tr.yml | 2 +- .../overviews/config/locales/crowdin/ja.yml | 2 +- .../recaptcha/config/locales/crowdin/tr.yml | 2 +- .../reporting/config/locales/crowdin/vi.yml | 2 +- .../storages/config/locales/crowdin/es.yml | 22 +-- .../storages/config/locales/crowdin/ja.yml | 2 +- .../storages/config/locales/crowdin/pt-BR.yml | 22 +-- .../storages/config/locales/crowdin/tr.yml | 2 +- .../storages/config/locales/crowdin/zh-CN.yml | 8 +- .../config/locales/crowdin/ja.yml | 18 +- 47 files changed, 495 insertions(+), 495 deletions(-) diff --git a/config/locales/crowdin/es.yml b/config/locales/crowdin/es.yml index 95a18eeb527..43d15583bbc 100644 --- a/config/locales/crowdin/es.yml +++ b/config/locales/crowdin/es.yml @@ -111,21 +111,21 @@ es: link: "webhook" mcp_configurations: index: - description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." - resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." - resources_submit: "Update resources" - tools_heading: "Tools" - tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." - tools_submit: "Update tools" + description: "El protocolo de contexto de modelo (MCP, por sus siglas en inglés) permite a los agentes de IA proporcionar a sus usuarios herramientas y recursos expuestos por esta instancia de OpenProject." + resources_heading: "Recursos" + resources_description: "OpenProject implementa las siguientes herramientas. Cada una de ellas puede habilitarse, renombrarse y describirse como usted desee. Para más información, consulte la [documentación sobre recursos del MCP](docs_url)." + resources_submit: "Actualizar recursos" + tools_heading: "Herramientas" + tools_description: "OpenProject implementa las siguientes herramientas. Cada una de ellas puede habilitarse, renombrarse y describirse como usted desee. Para más información, consulte la [documentación sobre herramientas del MCP](docs_url)." + tools_submit: "Actualizar herramientas" multi_update: - success: "MCP configurations were updated successfully." + success: "Las configuraciones del MCP se actualizaron correctamente." server_form: - description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + description_caption: "Cómo se describirá el servidor del MCP a otras aplicaciones que se conecten a él." + title_caption: "Un título corto que se muestra a las aplicaciones que se conectan al servidor del MCP." update: - failure: "MCP configuration could not be updated." - success: "MCP configuration was updated successfully." + failure: "No se ha podido actualizar la configuración del MCP." + success: "La configuración del MCP se ha actualizado correctamente." scim_clients: authentication_methods: sso: "JWT del proveedor de identidad" @@ -728,11 +728,11 @@ es: create_button: "Crear" name_label: "Nombre del token" created_dialog: - one_time_warning: "This is the only time you will see this token. Make sure to copy it now." + one_time_warning: "Esta es la única vez que verá este token. Asegúrese de copiarlo ahora." token/api: title: "Se ha generado el token de API" token/rss: - title: "The RSS token has been generated" + title: "Se ha generado el token de RSS" failed_to_reset_token: "No se pudo restablecer el token de acceso: %{error}" failed_to_create_token: "Error al crear el token de acceso: %{error}" failed_to_revoke_token: "Fallo al revocar el token de acceso: %{error}" @@ -1251,9 +1251,9 @@ es: port: "Puerto" tls_certificate_string: "Certificado SSL del servidor LDAP" mcp_configuration: - enabled: Enabled - title: Title - description: Description + enabled: Habilitado + title: Título + description: Descripción member: roles: "Perfiles" notification: @@ -1512,7 +1512,7 @@ es: even: "debe ser incluido." exclusion: "está reservado." feature_disabled: no está disponible. - feature_disabled_for_project: is disabled for this project. + feature_disabled_for_project: está desactivado para este proyecto. file_too_large: "es demasiado grande (el tamaño máximo es de %{count} Bytes)." filter_does_not_exist: "el filtro no existe." format: "no coincide con el formato esperado '%{expected}'." @@ -1945,8 +1945,8 @@ es: one: Token de acceso other: Tokens de acceso token/rss: - one: "RSS token" - other: "RSS tokens" + one: "Token RSS" + other: "Tokens RSS" type: one: "Tipo" other: "Tipos" @@ -2451,7 +2451,7 @@ es: baseline_comparison: Comparación de base de referencia board_view: Tableros avanzados calculated_values: Valores calculados - capture_external_links: Capture External Links + capture_external_links: Detección de enlaces externos internal_comments: Comentarios Internos custom_actions: Acciones personalizadas custom_field_hierarchies: Jerarquías @@ -2461,12 +2461,12 @@ es: edit_attribute_groups: Editar grupos de atributos gantt_pdf_export: Exportación de Gantt a PDF ldap_groups: Usuarios de LDAP y sincronización de grupos - mcp_server: MCP Server + mcp_server: Servidor MCP nextcloud_sso: Inicio de sesión único para almacenamiento en la nube one_drive_sharepoint_file_storage: Almacenamiento de archivos OneDrive/SharePoint placeholder_users: Usuarios de marcadores de posición portfolio_management: Gestión de carteras - project_creation_wizard: Project initiation request + project_creation_wizard: Solicitud de inicio de proyecto project_list_sharing: Compartición de la lista de proyectos readonly_work_packages: Paquetes de trabajo de solo lectura scim_api: API del servidor SCIM @@ -2508,7 +2508,7 @@ es: customize_life_cycle: description: "Cree y organice fases de proyecto diferentes a las previstas en la planificación del ciclo del proyecto PM2." capture_external_links: - description: "Prevent social engineering attacks by capturing and warning about external links before users visit them." + description: "Prevenga los ataques de ingeniería social detectando y advirtiendo sobre los enlaces externos antes de que los usuarios los visiten." work_package_query_relation_columns: description: "¿Necesita ver relaciones o elementos secundarios en la lista de paquetes de trabajo?" edit_attribute_groups: @@ -2539,7 +2539,7 @@ es: title: "Acciones personalizadas" description: "Las acciones personalizadas son accesos directos con un solo clic a un conjunto de acciones predefinidas que puede mostrar en determinados paquetes de trabajo según el estado, rol, tipo o proyecto." mcp_server: - description: "Integrate AI agents with your OpenProject instance through MCP." + description: "Integre agentes de IA con su instancia de OpenProject a través del protocolo de contexto de modelo (MCP)." nextcloud_sso: title: "Inicio de sesión único para almacenamiento en la nube" description: "Habilite una autenticación fluida y segura para su almacenamiento Nextcloud con el inicio de sesión único. Simplifique la gestión del acceso y mejore la comodidad de los usuarios." @@ -2552,7 +2552,7 @@ es: virus_scanning: description: "Asegúrese de que los archivos cargados en OpenProject se analizan en busca de virus antes de que otros usuarios puedan acceder a ellos." project_creation_wizard: - description: "Generate a step-by-step wizard to help project managers fill out a project initiation request." + description: "Genere un asistente paso a paso para ayudar a los gestores de proyectos a completar una solicitud de inicio de proyecto." placeholder_users: title: Usuarios de marcador de posición description: > @@ -2852,15 +2852,15 @@ es: #We need to include the version to invalidate outdated translations in other locales "17_1": new_features_title: > - The release contains various new features and improvements, such as: + El lanzamiento incluye varias funciones nuevas y mejoras, tales como: new_features_list: - line_0: Automated project initiation (Enterprise add-on). - line_1: "Meetings: add new or existing work packages as outcomes." - line_2: "Meetings: show iCal responses in OpenProject." - line_3: "Recurring meetings: duplicate agenda items to the next occurrence." - line_4: "Release to Community: Attribute highlighting." - line_5: Warning before opening external links in user-provided content (Enterprise add-on). - line_6: Improved performance and user experience, including the Activity tab and Documents module. + line_0: Inicio automatizado de proyectos (extensión Enterprise). + line_1: "Reuniones: añada paquetes de trabajo nuevos o existentes como resultados." + line_2: "Reuniones: muestre las respuestas de iCal en OpenProject." + line_3: "Reuniones periódicas: duplique los puntos del orden del día para el siguiente evento." + line_4: "Lanzamiento para la versión Community: resaltado de atributos." + line_5: Advertencia antes de abrir enlaces externos en el contenido que ha proporcionado un usuario (extensión Enterprise). + line_6: Mejora del rendimiento y de la experiencia de usuario, incluida la pestaña Actividad y el módulo Documentos. links: upgrade_enterprise_edition: "Actualizar a Enterprise" postgres_migration: "Migrando su instalación a PostgreSQL" @@ -2928,17 +2928,17 @@ es: instructions_after_error: "Puedes intentar entrar nuevamente haciendo clic sobre %{signin}. Si el error persiste, contacta a tu administrador para que te ayude." menus: admin: - ai: "Artificial Intelligence (AI)" + ai: "Inteligencia artificial (IA)" aggregation: "Agregación" api_and_webhooks: "API y webhooks" mail_notification: "Notificaciones de correo electrónico" mails_and_notifications: "Correos electrónicos y notificaciones" - mcp_configurations: "Model Context Protocol (MCP)" + mcp_configurations: "Protocolo de contexto de modelo (MCP)" quick_add: label: "Añadir…" my_account: access_tokens: - description: "Provider tokens are issued by OpenProject, allowing other applications to access it. Client tokens are issued by other applications, allowing OpenProject to access them." + description: "Los tokens de proveedor son emitidos por OpenProject y permiten a otras aplicaciones acceder a él. Los tokens de cliente son emitidos por otras aplicaciones y permiten a OpenProject acceder a ellos." no_results: title: "Ningún token de acceso que mostrar" description: "Todos ellos han sido desabilitados. Pueden ser rehabilitados en el menú de administración." @@ -2950,53 +2950,53 @@ es: simple_revoke_confirmation: "¿Seguro que desea revocar este token?" tabs: client: - title: "Client tokens" + title: "Tokens de cliente" provider: - title: "Provider tokens" + title: "Tokens de proveedor" token/api: - blank_description: "There is no API token yet. You can create one using the button below." - blank_title: "No API token" + blank_description: "Aún no hay tokens API. Puede crear uno utilizando el botón de abajo." + blank_title: "No hay tokens API" title: "API" - table_title: "API tokens" + table_title: "Tokens API" text_hint: "Los tokens API permiten a las aplicaciones de terceros comunicarse con esta instancia de OpenProject a través de las API REST." - static_token_name: "API token" + static_token_name: "Token API" disabled_text: "Los tokens API no están habilitados por el administrador. Póngase en contacto con su administrador para utilizar esta función." - add_button: "API token" + add_button: "Token API" ical: - blank_description: "To add an iCalendar token, subscribe to a new or existing calendar from within the Calendar module of a project. You must have the necessary permissions." - blank_title: "No iCalendar token" + blank_description: "Para añadir un token de iCalendar, suscríbase a un calendario nuevo o existente desde el módulo Calendario de un proyecto. Debe disponer de los permisos necesarios." + blank_title: "No hay token de iCalendar" title: "iCalendar" - table_title: "iCalendar tokens" + table_title: "Tokens de iCalendar" text_hint_link: "Los tokens iCalendar permiten a los usuarios [suscribirse a calendarios de OpenProject](docs_url) y ver la información actualizada de los paquetes de trabajo des de clientes externos." disabled_text: "Las suscripciones a iCalendar no están habilitadas por el administrador. Póngase en contacto con su administrador para utilizar esta función." oauth_application: - active_tokens: "Active tokens" - blank_description: "There is no third-party application access configured and active for you." - blank_title: "No OAuth application token" - last_used_at: "Last used at" + active_tokens: "Tokens activos" + blank_description: "No hay ningún acceso a aplicaciones de terceros configurado y activo para usted." + blank_title: "No hay token de aplicación OAuth" + last_used_at: "Utilizado por última vez el" title: "OAuth" - table_title: "OAuth application tokens" - text_hint: "OAuth application tokens allow third-party applications to connect with this OpenProject instance." + table_title: "Tokens de aplicación OAuth" + text_hint: "Los tokens de aplicación OAuth permiten a las aplicaciones de terceros conectarse con esta instancia de OpenProject." oauth_client: - blank_description: "There are no OAuth client tokens yet." - blank_title: "No OAuth client tokens" - failed: "An error occurred and the token couldn't be removed. Please try again later." - integration_type: "Integration type" - table_title: "OAuth client tokens" - text_hint: "OAuth client tokens allow this OpenProject instance to connect with external applications, such as file storages." + blank_description: "Aún no hay tokens de cliente de OAuth." + blank_title: "No hay tokens de cliente de OAuth" + failed: "Se ha producido un error y no se ha podido eliminar el token. Inténtelo de nuevo más tarde." + integration_type: "Tipo de integración" + table_title: "Tokens de cliente de OAuth" + text_hint: "Los tokens de cliente de OAuth permiten a esta instancia de OpenProject conectarse con aplicaciones externas, como almacenamientos de archivos." title: "OAuth" - remove_token: "Do you really want to remove this token? You will need to login again on %{integration}." - removed: "OAuth client token successfully removed" - unknown_integration: "Unknown" + remove_token: "¿Seguro que desea eliminar este token? Tendrá que volver a iniciar sesión en %{integration}." + removed: "Token de cliente de OAuth eliminado con éxito" + unknown_integration: "Desconocido" token/rss: - add_button: "RSS token" - blank_description: "There is no RSS token yet. You can create one using the button below." - blank_title: "No RSS token" + add_button: "Token RSS" + blank_description: "Aún no hay tokens RSS. Puede crear uno utilizando el botón de abajo." + blank_title: "No hay tokens RSS" title: "RSS" - table_title: "RSS tokens" - text_hint: "RSS tokens allow users to keep up with the latest changes in this OpenProject instance via an external RSS reader." - static_token_name: "RSS token" - disabled_text: "RSS tokens are not enabled by the administrator. Please contact your administrator to use this feature." + table_title: "Tokens RSS" + text_hint: "Los tokens RSS permiten a los usuarios mantenerse al día con los últimos cambios en esta instancia de OpenProject mediante un lector RSS externo." + static_token_name: "Token RSS" + disabled_text: "Los tokens RSS no están habilitados por el administrador. Póngase en contacto con su administrador para utilizar esta función." storages: unknown_storage: "Almacenamiento desconocido" notifications: @@ -3059,7 +3059,7 @@ es: label_always_visible: "Siempre se muestra" label_announcement: "Aviso" label_angular: "AngularJS" - label_app_modules: "%{app_title} modules" + label_app_modules: "Módulos %{app_title}" label_api_access_key: "Código de acceso API" label_api_access_key_created_on: "Código de acceso API creado hace %{value}" label_api_access_key_type: "API" @@ -3315,7 +3315,7 @@ es: label_journal_diff: "Comparación de la descripción" label_language: "Idioma" label_languages: "Idiomas" - label_external_links: "External links" + label_external_links: "Enlaces externos" label_locale: "Idioma y región" label_jump_to_a_project: "Saltar a un proyecto..." label_keyword_plural: "Palabras clave" @@ -4308,9 +4308,9 @@ es: setting_allowed_link_protocols: "Protocolos de enlace permitidos" setting_allowed_link_protocols_text_html: >- Permita que estos protocolos se muestren como enlaces en las descripciones de los paquetes de trabajo, los campos de texto largos y los comentarios. Por ejemplo, %{tel_code} o %{element_code}. Introduzca un protocolo por línea.
    Los protocolos %{http_code}, %{https_code} y %{mailto_code} siempre están permitidos. - setting_capture_external_links: "Capture external links" + setting_capture_external_links: "Detección de enlaces externos" setting_capture_external_links_text: > - When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + Cuando esta función está habilitada, todos los enlaces externos en texto formateado redirigirán a una página de advertencia antes de salir de la aplicación. Esto ayuda a proteger a los usuarios de sitios web externos potencialmente maliciosos. setting_after_first_login_redirect_url: "Primera redirección de inicio de sesión" setting_after_first_login_redirect_url_text_html: > Establezca una ruta para redirigir a los usuarios tras su primer inicio de sesión. Si está vacía, redirige a la página de inicio del recorrido de incorporación.
    Ejemplo: /my/page @@ -4324,16 +4324,16 @@ es: Si CORS está habilitado, estos son los orígenes que tienen permiso para acceder a la API de OpenProject.
    Consulte la documentación sobre el encabezado Origin para obtener información sobre cómo especificar los valores esperados. setting_apiv3_write_readonly_attributes: "Acceso de escritura a atributos de solo lectura" setting_apiv3_write_readonly_attributes_instructions: > - If enabled, the API will allow administrators to write static read-only attributes during creation, such as createdAt and author. + Si se activa, la API permitirá a los administradores escribir atributos estáticos de solo lectura durante la creación, como createdAt y author. setting_apiv3_write_readonly_attributes_warning: > - This setting has a use-case for e.g., importing data, but allows administrators to impersonate the creation of items as other users. All creation requests are being logged however with the true author. + Este ajuste tiene un caso de uso para, por ejemplo, la importación de datos, pero permite a los administradores suplantar la creación de elementos como otros usuarios. Sin embargo, todas las solicitudes de creación se registran con el verdadero autor. setting_apiv3_write_readonly_attributes_additional: > - For more information on attributes and supported resources, please see the %{api_documentation_link}. + Para obtener más información sobre los atributos y los recursos admitidos, consulte el enlace %{api_documentation_link}. setting_apiv3_max_page_size: "Tamaño máximo de página API" setting_apiv3_max_page_size_instructions: > - Set the maximum page size the API will respond with. It will not be possible to perform API requests that return more values on a single page. + Establezca el tamaño máximo de página con el que responderá la API. No se podrán realizar solicitudes a la API que devuelvan más valores en una sola página. setting_apiv3_max_page_size_warning: > - Please only change this value if you are sure why you need it. Setting to a high value will result in significant performance impacts, while a value lower than the per page options will cause errors in paginated views. + Por favor, cambie este valor solo si sabe con certeza por qué lo necesita. Un ajuste alto impactará significativamente el rendimiento, mientras que un valor inferior a las opciones por página causará errores en las vistas paginadas. setting_apiv3_docs: "Documentación" setting_apiv3_docs_enabled: "Activar página de documentos" setting_apiv3_docs_enabled_instructions_html: > @@ -4512,13 +4512,13 @@ es: not_writable: "La configuración no es modificable y solo puede cambiarla un administrador del sistema." failed_to_update: "No se pudo actualizar la configuración de «%{name}»: %{message}" authentication: - login: "Ingresar" + login: "Iniciar sesión" registration: "Registro" sso: "Inicio de sesión único (SSO)" omniauth_direct_login_hint_html: > Si esta opción está activa, las solicitudes de inicio de sesión se redirigirán al proveedor omniauth configurado. El menú desplegable de inicio de sesión y la página de inicio de sesión se desactivarán.
    Nota: A menos que también deshabilite los inicios de sesión con contraseña, con esta opción activada, los usuarios podrán seguir iniciando sesión internamente visitando la página de inicio de sesión %{internal_path}. remapping_existing_users_hint: > - If enabled, allows any configured identity provider to login existing users based on their username, even if the user never signed in through that provider before. This can be useful when migrating the OpenProject instance to a new SSO provider, but is not recommended when using a provider that is not trusted by all users of your instance. + Si está habilitado, permite a cualquier proveedor de identidad configurado iniciar sesión a los usuarios existentes basándose en su nombre de usuario, incluso si el usuario nunca ha iniciado sesión a través de ese proveedor anteriormente. Esto puede ser útil al migrar la instancia de OpenProject a un nuevo proveedor de SSO, pero no se recomienda cuando se usa un proveedor que no es confiable para todos los usuarios de su instancia. attachments: whitelist_text_html: > Define una lista de extensiones de archivo válidas o tipos MIME para los archivos cargados.
    Escriba las extensiones de archivo (por ejemplo, %{ext_example}) o tipos MIME (%{mime_example}).
    Deje vacío este campo para que pueda cargarse cualquier tipo de archivo. Se permiten varios valores (una línea para cada valor). @@ -4624,7 +4624,7 @@ es: project_mandate: "Mandato del proyecto" submission: description_template: > - **This work package was automatically created upon completion of the %{wizard_name} workflow.** A PDF artifact containing all submitted information has been generated and attached to this work package for reference and audit purposes. If you need to update or re-run the initiation steps, you can reopen the wizard at any time by using the link below: + **Este paquete de trabajo se ha creado automáticamente al completar el flujo de trabajo %{wizard_name}.** Se ha generado un artefacto en formato PDF con toda la información enviada y se ha adjuntado a este paquete de trabajo para fines de referencia y auditoría. Si necesitas actualizar o volver a ejecutar los pasos de inicio, puedes volver a abrir el asistente en cualquier momento utilizando el siguiente enlace: description: "Cuando un usuario envía una solicitud de inicio de proyecto, se creará un nuevo paquete de trabajo con el artefacto de solicitud adjunto como archivo PDF. La configuración siguiente define el tipo, el estado y el asignado de este nuevo paquete de trabajo." work_package_type: "Tipo de paquete de trabajo" work_package_type_caption: "El tipo de paquete de trabajo que debe utilizarse para almacenar el artefacto completado." @@ -4883,8 +4883,8 @@ es: other: "bloqueada temporalmente (%{count} intentos fallidos de inicio de sesión)" confirm_status_change: "Va a cambiar el estado de “%{name}”. ¿Está seguro de que quiere continuar?" deleted: "Usuarios Eliminados" - error_status_change_self: "You cannot change your own user status." - error_admin_change_on_non_admin: "Only administrators can change the status of administrator users." + error_status_change_self: "No puede cambiar su propio estado de usuario." + error_admin_change_on_non_admin: "Solo los administradores pueden cambiar el estado de los usuarios administradores." error_status_change_failed: "Cambiar la condición de usuario falló debido a los errores siguientes: %{errors}" invite: Invitar a un usuario por email invited: invitado @@ -5289,7 +5289,7 @@ es: hashed_token: display_value_placeholder: "***" external_link_warning: - title: "Leaving OpenProject" - warning_message: "You are about to leave OpenProject and visit an external website. Please be aware that external websites are not under our control and may have different privacy and security policies." - continue_message: "Are you sure you want to proceed to the following external link?" - continue_button: "Continue to external website" + title: "Salir de OpenProject" + warning_message: "Está a punto de salir de OpenProject y visitar un sitio web externo. Tenga en cuenta que los sitios web externos no están bajo nuestro control y pueden tener políticas de privacidad y seguridad diferentes." + continue_message: "¿Seguro que desea visitar el siguiente enlace externo?" + continue_button: "Continuar al sitio web externo" diff --git a/config/locales/crowdin/it.yml b/config/locales/crowdin/it.yml index 4a8c88e983a..c357a3e4492 100644 --- a/config/locales/crowdin/it.yml +++ b/config/locales/crowdin/it.yml @@ -2856,7 +2856,7 @@ it: new_features_list: line_0: Avvio automatico del progetto (componente aggiuntivo Enterprise). line_1: "Riunioni: aggiungi macro-attività nuove o esistenti come risultati." - line_2: "Riunioni: mostra le risposte dei partecipanti nelle sottoscrizioni iCal." + line_2: "Meetings: show iCal responses in OpenProject." line_3: "Riunioni ricorrenti: duplica i punti dell'ordine del giorno della prossima ricorrenza." line_4: "Rilascio alla community: evidenziazione degli attributi." line_5: Avviso prima dell'apertura di link esterni nei contenuti forniti dall'utente (componente aggiuntivo Enterprise). diff --git a/config/locales/crowdin/ja.yml b/config/locales/crowdin/ja.yml index 4b361276c3c..280aabc0208 100644 --- a/config/locales/crowdin/ja.yml +++ b/config/locales/crowdin/ja.yml @@ -321,7 +321,7 @@ ja: delete_dialog: title: "カスタムフィールド項目の削除" heading: "カスタムフィールド項目を削除しますか?" - description: "このアクションはアイテムとそのすべてのサブアイテムを不可逆的に削除します。割り当てられた値は完全に削除されます。 この項目が必要な場合、項目を削除すると既存の作業パッケージが無効になる可能性があります。" + description: "このアクションはアイテムとそのすべてのサブアイテムを不可逆的に削除します。割り当てられた値は完全に削除されます。 この項目が必要な場合、項目を削除すると既存のワークパッケージが無効になる可能性があります。" placeholder: label: "アイテムラベル" short: "ショートネーム" @@ -977,7 +977,7 @@ ja: manually_scheduled: "手動でスケジュールされました。日付はリレーションの影響を受けません。" blankslate: title: "前任者なし" - description: "自動スケジューリングを有効にするには、この作業パッケージには少なくとも1つの前任者が必要です。 その後、最も近い前任者の後に自動的に開始するようにスケジュールされます。" + description: "自動スケジューリングを有効にするには、このワークパッケージには少なくとも1つの前任者が必要です。 その後、最も近い前任者の後に自動的に開始するようにスケジュールされます。" ignore_non_working_days: title: "営業日のみ" mode: @@ -1008,7 +1008,7 @@ ja: copy_failed: "ワークパッケージをコピーできませんでした。" move_failed: "ワークパッケージを移動できませんでした。" could_not_be_saved: "次のワークパッケージを保存できませんでした:" - none_could_be_saved: "%{total} 作業パッケージのどれも更新できませんでした。" + none_could_be_saved: "%{total} ワークパッケージのどれも更新できませんでした。" x_out_of_y_could_be_saved: "%{failing} の %{total} ワークパッケージのうち、 %{success} を更新できませんでした。" selected_because_descendants: "%{selected} ワークパッケージが選択されている間、合計で %{total} ワークパッケージが子孫を含む影響を受けます。" descendant: "選択された子孫です" @@ -1246,7 +1246,7 @@ ja: mcp_configuration: enabled: Enabled title: Title - description: Description + description: 説明 member: roles: "ロール" notification: @@ -1271,7 +1271,7 @@ ja: false: "非公開" queries: "クエリ" status_code: "Status" - status_explanation: "Status description" + status_explanation: "ステータスの詳細" status_codes: not_started: "未着手" on_track: "順調" @@ -1360,7 +1360,7 @@ ja: is_closed: "作業が完了" is_readonly: "読み取り専用のワークパッケージ" excluded_from_totals: "階層内の合計の計算から除外" - default_done_ratio: "完成(%)" + default_done_ratio: "進捗度(%)" token/named: token_name: "トークン名" token/ical: @@ -1442,14 +1442,14 @@ ja: redirect_existing_links: "既存のリンクをリダイレクトする" text: "ページの内容" work_package: - ancestor: "と子・孫関係" #used for filtering of work packages that are descendants of a given work package + ancestor: "子・孫関係" #used for filtering of work packages that are descendants of a given work package begin_insertion: "挿入の始まり" begin_deletion: "削除の始まり" children: "サブ要素" derived_done_ratio: "完了率" - derived_remaining_hours: "残りの作業" - derived_remaining_time: "残りの作業" - done_ratio: "完成(%)" + derived_remaining_hours: "残時間" + derived_remaining_time: "残時間" + done_ratio: "進捗度(%)" duration: "期間" end_insertion: "挿入の終わり" end_deletion: "削除の終わり" @@ -1464,10 +1464,10 @@ ja: parent_issue: "親項目" parent_work_package: "親項目" priority: "優先度" - progress: "完成(%)" + progress: "進捗度(%)" readonly: "読み取り専用" - remaining_hours: "残りの作業" - remaining_time: "残りの作業" + remaining_hours: "残時間" + remaining_time: "残時間" shared_with_users: "共有先" schedule_manually: "手動スケジュール" spent_hours: "作業時間の記録" @@ -1786,10 +1786,10 @@ ja: assigned_to: format: "%{message}" done_ratio: - does_not_match_work_and_remaining_work: "仕事と残りの仕事が一致しません" + does_not_match_work_and_remaining_work: "予定時間と残時間が一致しません" cannot_be_set_when_work_is_zero: "作業時間が 0 の場合は設定できません" - must_be_set_when_remaining_work_is_set: "Remaining workが設定されている場合は必要。" - must_be_set_when_work_and_remaining_work_are_set: "作業と残りの作業が設定されている場合に必要です。" + must_be_set_when_remaining_work_is_set: "残時間が設定されている場合は必要。" + must_be_set_when_work_and_remaining_work_are_set: "作業と残時間が設定されている場合に必要です。" inclusion: "は0から100の間でなければなりません。" due_date: not_start_date: "は開始日になっていません。これはマイルストーンの場倍、必要である。" @@ -1822,16 +1822,16 @@ ja: does_not_exist: "指定されたカテゴリは存在しません。" estimated_hours: not_a_number: "は有効な日付ではありません。" - cant_be_inferior_to_remaining_work: "残りの作業よりも低くすることはできません。" - must_be_set_when_remaining_work_and_percent_complete_are_set: "残りの作業と % 完了が設定されている場合に必要です。" + cant_be_inferior_to_remaining_work: "残時間よりも低くすることはできません。" + must_be_set_when_remaining_work_and_percent_complete_are_set: "残時間と % 完了が設定されている場合に必要です。" remaining_hours: not_a_number: "は有効な日時ではありません。" - cant_exceed_work: "仕事より高くすることはできません。" + cant_exceed_work: "予定時間より高くすることはできません。" must_be_set_when_work_is_set: "作業が設定されている場合に必要です。" must_be_set_when_work_and_percent_complete_are_set: "「作業」と「完了率」が設定されている場合に必要です。" must_be_set_to_zero_hours_when_work_is_set_and_percent_complete_is_100p: '>- must be 0h when Work is set and % Complete is 100%.' must_be_empty_when_work_is_empty_and_percent_complete_is_100p: >- - 「Work」が空で「% Complete」が100%の場合、必ず空でなければなりません。 + 「予定時間」が空で「% Complete」が100%の場合、必ず空でなければなりません。 readonly_status: "ワークパッケージは読み取り専用の状態になっているため、その属性は変更できません。" type: attributes: @@ -2023,8 +2023,8 @@ ja: display_sums: "合計を表示" domain: "ドメイン" due_date: "終了日" - estimated_hours: "仕事" - estimated_time: "仕事" + estimated_hours: "予定時間" + estimated_time: "予定時間" email: "電子メールアドレス" entity_type: "エンティティ" expires_at: "有効期限" @@ -2070,7 +2070,7 @@ ja: state: "状態" subject: "タイトル" slug: "スラグ" - summary: "サマリ" + summary: "サマリー(概要)" template: "Template" time_zone: "タイムゾーン" text: "テキスト(本文)" @@ -2627,8 +2627,8 @@ ja: time_entry: "時系列ログが編集されました。" wiki_page: "Wiki ページが編集されました。" work_package_closed: "作業が完了" - work_package_edit: "仕事項目が編集されました。" - work_package_note: "仕事項目の注記が追加されました。" + work_package_edit: "ワークパッケージが編集されました" + work_package_note: "ワークパッケージに注記が追加されました" title: project: "プロジェクト: %{name}" subproject: "子プロジェクト: %{name}" @@ -2664,7 +2664,7 @@ ja: caption: "リスト内のすべてのワークパッケージの詳細レポートにワークパッケージをエクスポートします。" gantt: label: "ガントチャート" - caption: "ガントチャート表示で作業パッケージ一覧をエクスポートします。" + caption: "ガントチャート表示でワークパッケージ一覧をエクスポートします。" include_images: label: "画像を含める" caption: "PDFエクスポートのサイズを小さくするために画像を除外します。" @@ -2870,9 +2870,9 @@ ja: totals_removed_from_childless_work_packages: >- バージョンアップデートを伴う親でないワークパッケージに対して作業と進捗の合計が自動的に削除されます。 これはメンテナンス作業であり、無視することができます。 total_percent_complete_mode_changed_to_work_weighted_average: >- - 「Work」が含まれていない子作業パッケージは無視されます。 + 「予定時間」が含まれていない子ワークパッケージは無視されます。 total_percent_complete_mode_changed_to_simple_average: >- - 子作業パッケージの作業値が無視されています。 + 子ワークパッケージの作業値が無視されています。 links: configuration_guide: "設定ガイド" get_in_touch: "ご質問がありますか?ご連絡ください。" @@ -3432,7 +3432,7 @@ ja: label_privacy_policy: "データのプライバシーとセキュリティポリシー" label_product_version: "製品バージョ​​ン" label_profile: "プロファイル" - label_percent_complete: "完成(%)" + label_percent_complete: "進捗度(%)" label_progress_tracking: "進行状況の追跡" label_project: "プロジェクト" label_project_activity: "プロジェクトのアクティビティ" @@ -3480,7 +3480,7 @@ ja: label_relation_edit: "リレーションを編集" label_relation_new: "新しい関係" label_release_notes: "リリースノート" - label_remaining_work: "残りの作業" + label_remaining_work: "残時間" label_remove_column: "列を削除" label_remove_columns: "選択した列を削除" label_renamed: "名称変更" @@ -3548,7 +3548,7 @@ ja: label_subproject_plural: "子プロジェクト" label_subitems: "Subitems" label_subtask_plural: "子タスク" - label_summary: "サマリ" + label_summary: "サマリー(概要)" label_system: "システム" label_system_storage: "ストレージ情報" label_table_of_contents: "目次" @@ -3635,7 +3635,7 @@ ja: label_wiki_show_new_page_link: "下位のメニューで「子ページを新規作成」の項目を表示" label_wiki_show_submenu_item: "上位のメニュー項目" label_wiki_start: "開始ページ" - label_work: "仕事" + label_work: "予定時間" label_work_package: "ワーク パッケージ" label_work_package_attachments: "ワークパッケージの添付ファイル" label_work_package_category_new: "新規カテゴリ" @@ -3654,7 +3654,7 @@ ja: label_workflow: "ワークフロー" label_workflow_copy: "ワークフローをコピー" label_workflow_plural: "ワークフロー" - label_workflow_summary: "サマリ" + label_workflow_summary: "サマリー(概要)" label_working_days_and_hours: "稼働日・時間" label_x_closed_work_packages_abbr: one: "1件完了" @@ -3751,7 +3751,7 @@ ja: note: "注意: “%{note}”" sharing: work_packages: - allowed_actions: "この作業パッケージは %{allowed_actions} 可能です。プロジェクトのロールと権限によって変更できます。" + allowed_actions: "このワークパッケージは %{allowed_actions} 可能です。プロジェクトのロールと権限によって変更できます。" create_account: "このワークパッケージにアクセスするには、 %{instance} のアカウントを作成して有効化する必要があります。" open_work_package: "ワークパッケージを開く" subject: "ワークパッケージ%{id} があなたと共有されました" @@ -4021,7 +4021,7 @@ ja: permission_log_own_time: "作業時間の記録" permission_log_time: "他のユーザーの作業時間" permission_manage_forums: "フォーラムの管理" - permission_manage_categories: "仕事項目のカテゴリの管理" + permission_manage_categories: "ワークパッケージのカテゴリの管理" permission_manage_dashboards: "ダッシュボードの管理" permission_manage_work_package_relations: "ワークパッケージの関係の管理" permission_manage_members: "メンバーの管理" @@ -4259,7 +4259,7 @@ ja: search_input_placeholder: "検索…" setting_allowed_link_protocols: "許可されたリンクプロトコル" setting_allowed_link_protocols_text_html: >- - これらのプロトコルを、作業パッケージの説明、長文フィールド、およびコメント内でリンクとして表示できるようにします。例えば、%{tel_code} や %{element_code} などです。1行に1つのプロトコルを入力してください。
    %{http_code}、%{https_code}、および %{mailto_code} のプロトコルは常に許可されています。 + これらのプロトコルを、ワークパッケージの説明、長文フィールド、およびコメント内でリンクとして表示できるようにします。例えば、%{tel_code} や %{element_code} などです。1行に1つのプロトコルを入力してください。
    %{http_code}、%{https_code}、および %{mailto_code} のプロトコルは常に許可されています。 setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. @@ -4365,12 +4365,12 @@ ja: setting_work_package_done_ratio: "進行状況計算モード" setting_work_package_done_ratio_field: "ワークベース" setting_work_package_done_ratio_field_caption_html: >- - Complete %は自由に設定できる。オプションでWorkに値を入力すると、自動的にRemaining workが導き出される。 + Complete %は自由に設定できる。オプションで予定時間に値を入力すると、自動的に残時間が導き出される。 setting_work_package_done_ratio_status: "ステータスベース" setting_work_package_done_ratio_status_caption_html: >- 各ステータスには、 % Complete の値があります。ステータスを変更すると、 % Complete が変わります。 setting_work_package_done_ratio_explanation_html: > - ワーク・ベース・モードでは、完了(%)は自由に設定することができます。 オプションで「作業」に値を入力すると、自動的に「残りの作業」が導き出されます。 ステータス・ベース・モードでは、各ステータスに 完了(%) の値が関連付けられます。 ステータスを変更すると、完了(%) も変更される。 + ワーク・ベース・モードでは、完了(%)は自由に設定することができます。 オプションで「作業」に値を入力すると、自動的に「残時間」が導き出されます。 ステータス・ベース・モードでは、各ステータスに 完了(%) の値が関連付けられます。 ステータスを変更すると、完了(%) も変更される。 setting_work_package_properties: "項目名" setting_work_package_startdate_is_adddate: "今日の日付を新しいワークパッケージの開始日とする" setting_work_packages_projects_export_limit: "ワークパッケージ/プロジェクトのエクスポート制限" @@ -4394,10 +4394,10 @@ ja: setting_password_min_length: "最短長" setting_password_min_adhered_rules: "最小限の必要な文字の種類" setting_per_page_options: "オプションページごとの項目数" - setting_percent_complete_on_status_closed: "% ステータスが閉じられたときに完了します" + setting_percent_complete_on_status_closed: "ワークパッケージが終了したときの処理" setting_percent_complete_on_status_closed_no_change: "変更なし" setting_percent_complete_on_status_closed_no_change_caption_html: >- - 作業パッケージが閉じられても、完了率の値は変わりません。 + ワークパッケージが閉じられても、完了率の値は変わりません。 setting_percent_complete_on_status_closed_set_100p: "自動的に100%に設定" setting_percent_complete_on_status_closed_set_100p_caption: >- 終了したワークパッケージは完了したとみなされます。 @@ -4442,12 +4442,12 @@ ja: setting_sys_api_description: "リポジトリ管理Webサービスは、リポジトリへのアクセスの統合およびユーザー認証を提供します。" setting_time_format: "時間" setting_total_percent_complete_mode: "完了率階層合計の計算" - setting_total_percent_complete_mode_work_weighted_average: "仕事によるウェイト" + setting_total_percent_complete_mode_work_weighted_average: "予定時間によるウェイト" setting_total_percent_complete_mode_work_weighted_average_caption_html: >- - 総完了率は、階層内の各作業パッケージの作業に対して重み付けされる。作業のないワークパッケージは無視される。 + 総完了率は、階層内の各ワークパッケージの作業に対して重み付けされる。作業のないワークパッケージは無視される。 setting_total_percent_complete_mode_simple_average: "単純平均" setting_total_percent_complete_mode_simple_average_caption_html: >- - 作業は無視され、合計完了率は階層内の作業パッケージの完了(%)の単純平均値となります。 + 作業は無視され、合計完了率は階層内のワークパッケージの完了(%)の単純平均値となります。 setting_accessibility_mode_for_anonymous: "匿名ユーザ向けにアクセシビリティモードを有効" setting_user_format: "ユーザー名の形式" setting_user_default_timezone: "ユーザーのデフォルトのタイム ゾーン" @@ -4475,7 +4475,7 @@ ja: whitelist_text_html: > アップロードされたファイルに有効なファイル拡張子および/または MIME タイプのリストを定義します。
    ファイル拡張子(例. %{ext_example})またはMIMEタイプ(例、 %{mime_example}).
    任意のファイル タイプをアップロードできるようにするには、空のままにします。複数の値を指定できます (各値に 1 行)。 show_work_package_attachments: > - このオプションを無効にすると、新規プロジェクトの作業パッケージのファイルタブにある添付ファイルリストが非表示になります。ワークパッケージの説明に添付されたファイルは、内部添付ファイルストレージにアップロードされます。 + このオプションを無効にすると、新規プロジェクトのワークパッケージのファイルタブにある添付ファイルリストが非表示になります。ワークパッケージの説明に添付されたファイルは、内部添付ファイルストレージにアップロードされます。 antivirus: title: "ウイルススキャン" clamav_ping_failed: "ClamAVデーモンへの接続に失敗しました。設定を再確認して、もう一度試してください。" @@ -4647,7 +4647,7 @@ ja: text_are_you_sure: "よろしいですか?" open_link_in_a_new_tab: "Open link in a new tab" text_are_you_sure_continue: "続行してもよろしいですか?" - text_are_you_sure_with_children: "仕事項目と子項目を削除してもよろしいですか?" + text_are_you_sure_with_children: "ワークパッケージと子項目を削除してもよろしいですか?" text_are_you_sure_with_project_custom_fields: "この属性を削除すると、すべてのプロジェクトの値も削除されます。よろしいですか?" text_are_you_sure_with_project_life_cycle_step: "このフェーズを削除すると、すべてのプロジェクトでの使用も削除されます。よろしいですか?" text_assign_to_project: "プロジェクト自体に割り当てする" @@ -4695,7 +4695,7 @@ ja: text_default_administrator_account_changed: "管理者アカウントでデフォルト設定が変更済み" text_default_encoding: "既定値: UTF-8" text_destroy: "削除" - text_destroy_with_associated: "削除される仕事項目と追加の対象物が関連付けています。それらの対象物は次の種類です:" + text_destroy_with_associated: "削除されるワークパッケージと追加の対象物が関連付けています。それらの対象物は次の種類です:" text_destroy_what_to_do: "どれかを選択して下さい。" text_diff_truncated: "... 差分の行数が表示可能な上限を超えました。超過分は表示しません。" text_email_delivery_not_configured: "メール配信が設定されておらず、通知が無効になっています。\nSMTPサーバーを有効にしてください。" @@ -4897,23 +4897,23 @@ ja: label_note: "注:" modal: work_based_help_text: "各フィールドは、可能な限り他の2つのフィールドから自動的に計算される。" - status_based_help_text: "完了率は作業パッケージのステータスによって設定される。" - migration_warning_text: "作業ベースの進捗計算モードでは、「完了率」を手動で設定することはできず、「作業」に関連付けられます。既存の値は保持されていますが、編集することはできません。まずWorkを入力してください。" + status_based_help_text: "完了率はワークパッケージのステータスによって設定される。" + migration_warning_text: "作業ベースの進捗計算モードでは、「完了率」を手動で設定することはできず、「作業」に関連付けられます。既存の値は保持されていますが、編集することはできません。まず予定時間を入力してください。" derivation_hints: done_ratio: cleared_because_remaining_work_is_empty: "残務がないためクリア。" - cleared_because_work_is_0h: "Work が 0h のためクリアされる。" - derived: "仕事」と「残り仕事」から派生したもの。" + cleared_because_work_is_0h: "予定時間 が 0h のためクリアされる。" + derived: "「予定時間」と「残時間」から派生したもの。" estimated_hours: - cleared_because_remaining_work_is_empty: "残りの作業が空のため消去されました。" - derived: "残りの作業と % 完了から派生しました。" - same_as_remaining_work: "残りの作業と同じ値に設定します。" + cleared_because_remaining_work_is_empty: "残時間が空のため消去されました。" + derived: "残時間と % 完了から派生しました。" + same_as_remaining_work: "残時間と同じ値に設定します。" remaining_hours: cleared_because_work_is_empty: "ワークが空のためクリア。" cleared_because_percent_complete_is_empty: "完了(%) が空のため、クリアされました。" decreased_by_delta_like_work: "%{delta}によって減少しました。作業の減少に一致します。" derived: "作品と完成度から派生したもの。" - increased_by_delta_like_work: "%{delta}、仕事の増加に合わせて増加した。" + increased_by_delta_like_work: "%{delta}、予定時間の増加に合わせて増加した。" same_as_work: "ワークと同じ値に設定する。" permissions: comment: "コメント" diff --git a/config/locales/crowdin/js-es.yml b/config/locales/crowdin/js-es.yml index 27042a47fee..b2505deda62 100644 --- a/config/locales/crowdin/js-es.yml +++ b/config/locales/crowdin/js-es.yml @@ -203,8 +203,8 @@ es: add_table: "Agregar tabla de paquetes de trabajo relacionados" edit_query: "Editar consulta" new_group: "Nuevo grupo" - delete_group: "Delete group" - remove_attribute: "Remove from group" + delete_group: "Eliminar grupo" + remove_attribute: "Eliminar del grupo" reset_to_defaults: "Restablecer valores predeterminados" working_days: calendar: @@ -426,7 +426,7 @@ es: label_remove_row: "Eliminar fila" label_report: "Reportar" label_repository_plural: "Repositorios" - label_resize_project_menu: "Resize project menu" + label_resize_project_menu: "Cambiar el tamaño del menú del proyecto" label_save_as: "Guardad como" label_search_columns: "Buscar una columna" label_select_watcher: "Selecciona un observador..." diff --git a/config/locales/crowdin/js-ja.yml b/config/locales/crowdin/js-ja.yml index eb2d9c4f72b..7121902ba95 100644 --- a/config/locales/crowdin/js-ja.yml +++ b/config/locales/crowdin/js-ja.yml @@ -296,7 +296,7 @@ ja: ical_sharing_modal: title: "カレンダーを購読する" inital_setup_error_message: "データ取得中にエラーが発生しました。" - description: "URL(iCalendar)を使用して、外部クライアントでこのカレンダーを購読し、そこから最新の作業パッケージ情報を表示することができます。" + description: "URL(iCalendar)を使用して、外部クライアントでこのカレンダーを購読し、そこから最新のワークパッケージ情報を表示することができます。" warning: "このURLを他のユーザーと共有しないでください。このリンクを持つ誰でもアカウントやパスワードなしでワークパッケージの詳細を表示することができます。" token_name_label: "どこで使うのですか??" token_name_placeholder: '名前を入力してください。例:"電話"' @@ -468,7 +468,7 @@ ja: label_watcher_added_successfully: "ウォッチャーが正常に追加されました !" label_watcher_deleted_successfully: "ウォッチャーが正常に削除されました !" label_work_package_details_you_are_here: "あなたは %{type} %{subject}の %{tab} タブを表示しています。" - label_work_package_context_menu: "作業パッケージのコンテキストメニュー" + label_work_package_context_menu: "ワークパッケージのコンテキストメニュー" label_unwatch: "ウォッチしない" label_unwatch_work_package: "ワークパッケージのウォッチを削除" label_uploaded_by: "アップロードした人" @@ -525,7 +525,7 @@ ja: add: "プラス(+)アイコンをクリックして新しいカードを作成するか、既存のカードをボード上のリストに追加する。" drag: "リスト内のカードをドラッグ・アンド・ドロップして並び替えたり、別のリストに移動することができます。
    右上の情報(i)アイコンをクリックするか、カードをダブルクリックして詳細を開くことができます。" wp: - toggler: "次に、作業パッケージ セクションを見てみましょう。このセクションでは、あなたの作業についてより詳細な情報を確認できます。" + toggler: "次に、ワークパッケージ セクションを見てみましょう。このセクションでは、あなたの作業についてより詳細な情報を確認できます。" list: "このワークパッケージの概要では、タスク、マイルストーン、フェーズなど、プロジェ クト内のすべての作業を一覧表示します。
    ワークパッケージは、このビューから直接作成・編集することができます。特定のワークパッケージの詳細を見るには、その行をダブルクリックします。" full_view: "ワークパッケージ詳細ビューは、ワークパッケージの説明、ステータス、優先度、アクティビティ、依存関係、コメントなど、ワークパッケージに関連するすべての情報を提供する。" back_button: "終了してワークパッケージリストに戻るには、左上隅のリターン矢印を使用します。" @@ -534,10 +534,10 @@ ja: timeline: "ここでは、プロジェクト計画を編集したり、タスク、マイルストーン、フェーズなどの新しいワークパッケージを作成したり、依存関係を追加したりすることができます。チームメンバー全員がいつでも最新の計画を確認し、更新することができます。" team_planner: overview: "チームプランナーを使えば、チームメンバーにタスクを視覚的に割り当て、誰が何に取り組んでいるかの概要を把握することができます。" - calendar: "週または隔週の計画ボードには、チームメンバーに割り当てられたすべての作業パッケージが表示されます。" + calendar: "週または隔週の計画ボードには、チームメンバーに割り当てられたすべてのワークパッケージが表示されます。" add_assignee: "開始するには、チームプランナーに担当者を追加します。" add_existing: "既存のワークパッケージを検索し、チームプランナーにドラッグすると、即座にチームメンバーに割り当て、開始日と終了日を定義できます。" - card: "作業パッケージを水平方向にドラッグして時間を前後させたり、端をドラッグして開始日と終了日を変更したり、垂直方向にドラッグして別の行に移動して別のメンバーに割り当てることもできます。" + card: "ワークパッケージを水平方向にドラッグして時間を前後させたり、端をドラッグして開始日と終了日を変更したり、垂直方向にドラッグして別の行に移動して別のメンバーに割り当てることもできます。" notifications: title: "通知" no_unread: "未読通知がない" @@ -654,7 +654,7 @@ ja: context: "プロジェクトのコンテキスト" not_available: "プロジェクトなし" required_outside_context: > - 作業パッケージを作成するプロジェクトを選択して、すべての属性を確認してください。 上記で有効になっているタイプのプロジェクトのみ選択できます。 + ワークパッケージを作成するプロジェクトを選択して、すべての属性を確認してください。 上記で有効になっているタイプのプロジェクトのみ選択できます。 reminders: settings: daily: @@ -698,7 +698,7 @@ ja: error_no_table_configured: "%{group} のテーブルを設定してください。" reset_title: "フォーム設定をリセット" confirm_reset: > - 警告:フォーム設定をリセットしてもよろしいですか? これにより、属性が規定のグループにリセットされ、すべてのカスタムフィールドが無効になります。   コンテキスト| リクエストコンテキスト + 警告:フォーム設定をリセットしてもよろしいですか? これにより、属性がデフォルトのグループにリセットされ、すべてのカスタムフィールドが無効になります。   コンテキスト| リクエストコンテキスト upgrade_to_ee: "エンタープライズ・オンプレミス版へのアップグレード" upgrade_to_ee_text: "すごい!このアドオンが必要なら、あなたはスーパープロです!エンタープライズ版のクライアントになって、私たちオープンソース開発者をサポートしていただけませんか?" more_information: "詳細情報" @@ -711,7 +711,7 @@ ja: timer: start_new_timer: "新しいタイマーを開始する" timer_already_running: "新しいタイマーを開始するには、まず現在のタイマーを停止する必要があります:" - timer_already_stopped: "この作業パッケージのアクティブなタイマーがありません。別のウィンドウで停止しましたか?" + timer_already_stopped: "このワークパッケージのアクティブなタイマーがありません。別のウィンドウで停止しましたか?" tracking_time: "トラッキング・タイム" button_stop: "現在のタイマーを停止する" two_factor_authentication: @@ -885,7 +885,7 @@ ja: limited_results: 手動ソートモードで表示できるのは、 %{count} ワークパッケージだけです。フィルタリングするか、自動ソートに切り替えてください。 property_groups: details: "詳細" - people: "人" + people: "割り当て" estimatesAndTime: "見積もりと 時間" other: "その他" properties: @@ -894,7 +894,7 @@ ja: createdAt: "作成日時" description: "説明" date: "日付" - percentComplete: "完成(%)" + percentComplete: "進捗度(%)" percentCompleteAlternative: "進捗状況" dueDate: "終了日" duration: "期間" @@ -903,7 +903,7 @@ ja: percentageDone: "完了比率" priority: "優先度" projectName: "プロジェクト" - remainingWork: "残りの作業" + remainingWork: "残時間" remainingWorkAlternative: "残り時間" responsible: "責任者" startDate: "開始日" @@ -917,16 +917,16 @@ ja: version: "バージョン" work: "仕事" workAlternative: "予定工数" - remainingTime: "残りの作業" + remainingTime: "残時間" default_queries: manually_sorted: "新しい手動ソートされたクエリ" latest_activity: "最新の活動" created_by_me: "自分が作成したもの" - assigned_to_me: "自分の担当するもの" + assigned_to_me: "自分が担当しているもの" recently_created: "最近作成されたもの" all_open: "未完了" overdue: "期限の過ぎたもの" - summary: "サマリ" + summary: "サマリー(概要)" shared_with_users: "ユーザーと共有" shared_with_me: "共有された項目" jump_marks: diff --git a/config/locales/crowdin/js-pt-BR.yml b/config/locales/crowdin/js-pt-BR.yml index 0c9d79dea7e..0665eff1c2e 100644 --- a/config/locales/crowdin/js-pt-BR.yml +++ b/config/locales/crowdin/js-pt-BR.yml @@ -202,8 +202,8 @@ pt-BR: add_table: "Adicionar tabela de pacotes de trabalho relacionados" edit_query: "Editar consulta" new_group: "Novo grupo" - delete_group: "Delete group" - remove_attribute: "Remove from group" + delete_group: "Excluir grupo" + remove_attribute: "Remover do grupo" reset_to_defaults: "Voltar à configuração original" working_days: calendar: @@ -425,7 +425,7 @@ pt-BR: label_remove_row: "Remover linha" label_report: "Relatório" label_repository_plural: "Repositórios" - label_resize_project_menu: "Resize project menu" + label_resize_project_menu: "Redimensionar menu do projeto" label_save_as: "Salvar como" label_search_columns: "Buscar uma coluna" label_select_watcher: "Selecione um observador..." diff --git a/config/locales/crowdin/js-tr.yml b/config/locales/crowdin/js-tr.yml index 22ed8514c11..78ce77f2b82 100644 --- a/config/locales/crowdin/js-tr.yml +++ b/config/locales/crowdin/js-tr.yml @@ -218,8 +218,8 @@ tr: removed_title: "Aşağıdaki günleri çalışılmayan günler listesinden çıkaracaksınız:" change_description: "Haftanın hangi günlerinin iş günü veya iş günü olmayan günler olarak kabul edileceğinin değiştirilmesi, bu sistemdeki tüm projelerdeki tüm iş paketlerinin ve yaşam döngülerinin başlangıç ve bitiş günlerini etkileyebilir." warning: > - The changes might take some time to take effect. You will be notified when all relevant work packages and project life cycles have been updated. - Are you sure you want to continue? + Değişikliklerin yürürlüğe girmesi biraz zaman alabilir. İlgili tüm iş paketleri ve proje yaşam döngüleri güncellendiğinde size bildirilecektir. + Devam etmek istediğinizden emin misiniz? work_packages_settings: warning_progress_calculation_mode_change_from_status_to_field_html: >- Changing progress calculation mode from status-based to work-based will make the % Complete field freely editable. If you optionally enter values for Work or Remaining work, they will also be linked to % Complete. Changing Remaining work can then update % Complete. @@ -558,7 +558,7 @@ tr: date_alerts: milestone_date: "Kilometre taşı tarihi" overdue: "Süresi geçmiş" - overdue_since: "for %{difference_in_days}." + overdue_since: "%{difference_in_days} içinde." property_today: "bugün." property_is: "%{difference_in_days} içinde." property_was: "%{difference_in_days} önce idi." @@ -764,7 +764,7 @@ tr: update_relation: "İlişki türünü değiştirmek için tıklayın" show_relations: "İlişkileri göster" add_predecessor: "Ekle (Öncül)" - add_successor: "Ekle (Ardıl)" + add_successor: "Ardıl görev ekle" remove: "İlişkiyi kaldır" save: "İlişkiyi kaydet" abort: "İptal" diff --git a/config/locales/crowdin/js-uk.yml b/config/locales/crowdin/js-uk.yml index 5b026641325..5d623bf7906 100644 --- a/config/locales/crowdin/js-uk.yml +++ b/config/locales/crowdin/js-uk.yml @@ -426,7 +426,7 @@ uk: label_remove_row: "Видалити рядок" label_report: "Звіт" label_repository_plural: "Репозиторії" - label_resize_project_menu: "Змінити розмір меню проекту" + label_resize_project_menu: "Змінити розмір меню проєкту" label_save_as: "Зберегти як" label_search_columns: "Пошук стовпця" label_select_watcher: "Виберіть спостерігача..." diff --git a/config/locales/crowdin/ko.yml b/config/locales/crowdin/ko.yml index b3356a41892..a54c25625ff 100644 --- a/config/locales/crowdin/ko.yml +++ b/config/locales/crowdin/ko.yml @@ -2810,7 +2810,7 @@ ko: new_features_list: line_0: 자동화된 프로젝트 시작(Enterprise 추가 기능). line_1: "미팅: 새 작업 패키지 또는 기존 작업 패키지를 결과로 추가합니다." - line_2: "미팅: iCal 구독에서 참가자 응답을 표시합니다." + line_2: "Meetings: show iCal responses in OpenProject." line_3: "반복 미팅: 의제 항목을 다음 항목에 복제합니다." line_4: "커뮤니티에 릴리스: 특성이 강조 표시됩니다." line_5: 사용자 제공 콘텐츠에서 외부 링크를 열기 전에 표시되는 경고입니다(Enterprise 추가 기능). diff --git a/config/locales/crowdin/pl.yml b/config/locales/crowdin/pl.yml index 14d8983bfde..7cd49027655 100644 --- a/config/locales/crowdin/pl.yml +++ b/config/locales/crowdin/pl.yml @@ -2953,7 +2953,7 @@ pl: new_features_list: line_0: Zautomatyzowane inicjowanie projektów (dodatek wersji Enterprise). line_1: "Spotkania: dodawaj nowe lub istniejące pakiety robocze jako wyniki." - line_2: "Spotkania: pokaż odpowiedzi uczestników w subskrypcjach iCal." + line_2: "Meetings: show iCal responses in OpenProject." line_3: "Spotkania cykliczne: duplikuj punkty planu spotkania w następnym wystąpieniu." line_4: "Udostępnianie społeczności: wyróżnianie atrybutów." line_5: Ostrzeżenie przed otwarciem linków zewnętrznych w treści dostarczonej przez użytkownika (dodatek wersji Enterprise). diff --git a/config/locales/crowdin/pt-BR.yml b/config/locales/crowdin/pt-BR.yml index 017765746b6..872094f1a89 100644 --- a/config/locales/crowdin/pt-BR.yml +++ b/config/locales/crowdin/pt-BR.yml @@ -111,21 +111,21 @@ pt-BR: link: "webhook" mcp_configurations: index: - description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." - resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." - resources_submit: "Update resources" - tools_heading: "Tools" - tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." - tools_submit: "Update tools" + description: "O protocolo de contexto do modelo permite que agentes de IA forneçam aos seus usuários ferramentas e recursos disponibilizados por esta instância do OpenProject." + resources_heading: "Recursos" + resources_description: "O OpenProject implementa as seguintes ferramentas. Cada uma pode ser habilitada, renomeada e descrita conforme desejado. Para mais informações, consulte a [documentação sobre recursos MCP](docs_url)." + resources_submit: "Atualizar recursos" + tools_heading: "Ferramentas" + tools_description: "O OpenProject implementa as seguintes ferramentas. Cada uma pode ser habilitada, renomeada e descrita conforme desejado. Para mais informações, consulte a [documentação sobre ferramentas MCP](docs_url)." + tools_submit: "Atualizar ferramentas" multi_update: - success: "MCP configurations were updated successfully." + success: "Configurações MCP atualizadas com sucesso." server_form: - description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + description_caption: "Como o servidor MCP será descrito para outros aplicativos que se conectarem a ele." + title_caption: "Um título curto exibido para aplicativos que se conectam ao servidor MCP." update: - failure: "MCP configuration could not be updated." - success: "MCP configuration was updated successfully." + failure: "A configuração MCP não pôde ser atualizada." + success: "Configuração MCP atualizada com sucesso." scim_clients: authentication_methods: sso: "JWT do provedor de identidade" @@ -729,11 +729,11 @@ pt-BR: create_button: "Criar" name_label: "Nome do token" created_dialog: - one_time_warning: "This is the only time you will see this token. Make sure to copy it now." + one_time_warning: "Esta é a única vez que você verá este token. Certifique-se de copiá-lo agora." token/api: title: "O token de API foi gerado" token/rss: - title: "The RSS token has been generated" + title: "O token de RSS foi gerado" failed_to_reset_token: "Falha ao redefinir o token de acesso: %{error}" failed_to_create_token: "Falha ao criar o token de acesso: %{error}" failed_to_revoke_token: "Falha ao revogar o token de acesso: %{error}" @@ -1251,9 +1251,9 @@ pt-BR: port: "Porta" tls_certificate_string: "Certificado SSL do servidor LDAP" mcp_configuration: - enabled: Enabled - title: Title - description: Description + enabled: Habilitado + title: Título + description: Descrição member: roles: "Papéis" notification: @@ -1512,7 +1512,7 @@ pt-BR: even: "deve ser par." exclusion: "está reservado." feature_disabled: não está disponível. - feature_disabled_for_project: is disabled for this project. + feature_disabled_for_project: está desativado para este projeto. file_too_large: "é muito grande (tamanho máximo é %{count} Bytes)." filter_does_not_exist: "filtro não existe." format: "não corresponde ao formato '%{expected}' esperado." @@ -1945,8 +1945,8 @@ pt-BR: one: Token de acesso other: Tokens de acesso token/rss: - one: "RSS token" - other: "RSS tokens" + one: "Token RSS" + other: "Tokens RSS" type: one: "Tipo" other: "Tipos" @@ -2451,7 +2451,7 @@ pt-BR: baseline_comparison: Comparações de linha base board_view: Quadros avançados calculated_values: Valores calculados - capture_external_links: Capture External Links + capture_external_links: Capturar links externos internal_comments: Comentários internos custom_actions: Ações Personalizadas custom_field_hierarchies: Hierarquias @@ -2461,12 +2461,12 @@ pt-BR: edit_attribute_groups: Editar atributo de grupos gantt_pdf_export: Exportar diagrama de Gantt para PDF ldap_groups: Sincronização de usuários e grupos LDAP - mcp_server: MCP Server + mcp_server: Servidor MCP nextcloud_sso: Autenticação única para o armazenamento do Nextcloud one_drive_sharepoint_file_storage: Armazenamento de Arquivos OneDrive/SharePoint placeholder_users: Usuários Genéricos portfolio_management: Gestão de portfólio - project_creation_wizard: Project initiation request + project_creation_wizard: Solicitação de início de projeto project_list_sharing: Compartilhamento de Lista de Projetos readonly_work_packages: Pacotes de Trabalho somente leitura scim_api: API do servidor SCIM @@ -2508,7 +2508,7 @@ pt-BR: customize_life_cycle: description: "Criar e organizar fases do projeto diferentes das fornecidas pelo planejamento do ciclo de projeto PM2." capture_external_links: - description: "Prevent social engineering attacks by capturing and warning about external links before users visit them." + description: "Evite ataques de engenharia social detectando e alertando sobre links externos antes que os usuários os acessem." work_package_query_relation_columns: description: "Precisa visualizar as relações ou elementos filhos na lista de pacotes de trabalho?" edit_attribute_groups: @@ -2539,7 +2539,7 @@ pt-BR: title: "Ações personalizadas" description: "As ações personalizadas são atalhos de clique único para um conjunto de ações pré-definidas que você pode disponibilizar em certos pacotes de trabalho com base no estado, função, tipo ou projeto." mcp_server: - description: "Integrate AI agents with your OpenProject instance through MCP." + description: "Integre agentes de IA à sua instância do OpenProject por meio do MCP." nextcloud_sso: title: "Autenticação única para o armazenamento do Nextcloud" description: "Ative a autenticação segura e contínua para o seu armazenamento Nextcloud com Autenticação única. Simplifique o gerenciamento de acessos e melhore a experiência do usuário." @@ -2552,7 +2552,7 @@ pt-BR: virus_scanning: description: "Garantir que os arquivos carregados no OpenProject sejam verificados quanto à presença de vírus antes de serem acessados por outros usuários." project_creation_wizard: - description: "Generate a step-by-step wizard to help project managers fill out a project initiation request." + description: "Gerar um assistente detalhado para ajudar os gerentes de projeto a preencher uma solicitação de início de projeto." placeholder_users: title: Usuários de espaço reservado description: > @@ -2852,15 +2852,15 @@ pt-BR: #We need to include the version to invalidate outdated translations in other locales "17_1": new_features_title: > - The release contains various new features and improvements, such as: + A versão traz diversos novos recursos e melhorias, como: new_features_list: - line_0: Automated project initiation (Enterprise add-on). - line_1: "Meetings: add new or existing work packages as outcomes." - line_2: "Meetings: show iCal responses in OpenProject." - line_3: "Recurring meetings: duplicate agenda items to the next occurrence." - line_4: "Release to Community: Attribute highlighting." - line_5: Warning before opening external links in user-provided content (Enterprise add-on). - line_6: Improved performance and user experience, including the Activity tab and Documents module. + line_0: Início de projeto automatizado (complementos Enterprise). + line_1: "Reuniões: adicionar pacotes de trabalho novos ou existentes como resultados." + line_2: "Reuniões: exibir respostas do iCal no OpenProject." + line_3: "Reuniões recorrentes: duplicar itens da pauta para a próxima ocorrência." + line_4: "Versão para a comunidade: Realce de atributos." + line_5: Aviso antes de abrir links externos em conteúdo fornecido pelo usuário (complementos Enterprise). + line_6: Desempenho e experiência do usuário aprimorados, incluindo a aba Atividade e o módulo Documentos. links: upgrade_enterprise_edition: "Atualizar para a edição Enterprise" postgres_migration: "Migrando sua instalação para PostgreSQL" @@ -2928,17 +2928,17 @@ pt-BR: instructions_after_error: "Você pode tentar entrar novamente, clicando em %{signin}. Se o erro persistir, consulte seu administrador para obter ajuda." menus: admin: - ai: "Artificial Intelligence (AI)" + ai: "Inteligência artificial (IA)" aggregation: "Agregação" api_and_webhooks: "API e webhooks" mail_notification: "Notificações por e-mail" mails_and_notifications: "E-mails e notificações" - mcp_configurations: "Model Context Protocol (MCP)" + mcp_configurations: "Protocolo de Contexto de Modelo (MCP)" quick_add: label: "Adicionar…" my_account: access_tokens: - description: "Provider tokens are issued by OpenProject, allowing other applications to access it. Client tokens are issued by other applications, allowing OpenProject to access them." + description: "Tokens de provedor são emitidos pelo OpenProject, permitindo que outros aplicativos tenham acesso a ele. Tokens de cliente são emitidos por outros aplicativos, permitindo que o OpenProject tenha acesso a eles." no_results: title: "Nenhum tokens de acesso para exibir" description: "Todos foram desativados. Eles podem ser re-ativados no menu Administração." @@ -2950,53 +2950,53 @@ pt-BR: simple_revoke_confirmation: "Tem certeza de que deseja revogar este token?" tabs: client: - title: "Client tokens" + title: "Tokens do cliente" provider: - title: "Provider tokens" + title: "Tokens de provedor" token/api: - blank_description: "There is no API token yet. You can create one using the button below." - blank_title: "No API token" + blank_description: "Ainda não há um token de API. Você pode criar um usando o botão abaixo." + blank_title: "Sem token de API" title: "API" - table_title: "API tokens" + table_title: "Tokens de API" text_hint: "Os tokens de API permitem que aplicativos de terceiros se comuniquem com esta instância do OpenProject por meio de APIs REST." - static_token_name: "API token" + static_token_name: "Token de API" disabled_text: "Os tokens de API não estão habilitados pelo administrador. Entre em contato com o administrador para utilizar este recurso." - add_button: "API token" + add_button: "Token de API" ical: - blank_description: "To add an iCalendar token, subscribe to a new or existing calendar from within the Calendar module of a project. You must have the necessary permissions." - blank_title: "No iCalendar token" + blank_description: "Para adicionar um token iCalendar, assine um calendário novo ou existente pelo módulo Calendário de um projeto. É necessário ter as permissões adequadas." + blank_title: "Sem token iCalendar" title: "iCalendar" - table_title: "iCalendar tokens" + table_title: "Tokens iCalendar" text_hint_link: "Tokens de iCalendar permitem que os usuários [assinem calendários do OpenProject](docs_url) e vejam informações atualizadas dos pacotes de trabalho de clientes externos." disabled_text: "As assinaturas do iCalendar não estão habilitadas pelo administrador. Entre em contato com o administrador para utilizar este recurso." oauth_application: - active_tokens: "Active tokens" - blank_description: "There is no third-party application access configured and active for you." - blank_title: "No OAuth application token" - last_used_at: "Last used at" + active_tokens: "Tokens ativos" + blank_description: "Não há nenhum acesso ativo de aplicativos de terceiros configurado para sua conta." + blank_title: "Sem token de aplicativo OAuth" + last_used_at: "Último uso em" title: "OAuth" - table_title: "OAuth application tokens" - text_hint: "OAuth application tokens allow third-party applications to connect with this OpenProject instance." + table_title: "Tokens de aplicativo OAuth" + text_hint: "Tokens de aplicativo OAuth permitem que aplicativos de terceiros se conectem a esta instância do OpenProject." oauth_client: - blank_description: "There are no OAuth client tokens yet." - blank_title: "No OAuth client tokens" - failed: "An error occurred and the token couldn't be removed. Please try again later." - integration_type: "Integration type" - table_title: "OAuth client tokens" - text_hint: "OAuth client tokens allow this OpenProject instance to connect with external applications, such as file storages." + blank_description: "Ainda não há tokens de cliente OAuth." + blank_title: "Sem tokens de cliente OAuth" + failed: "Ocorreu um erro e o token não pôde ser removido. Tente novamente mais tarde." + integration_type: "Tipo de integração" + table_title: "Tokens de cliente OAuth" + text_hint: "Tokens de cliente OAuth permitem que esta instância do OpenProject se conecte a aplicativos externos, como serviços de armazenamento de arquivos." title: "OAuth" - remove_token: "Do you really want to remove this token? You will need to login again on %{integration}." - removed: "OAuth client token successfully removed" - unknown_integration: "Unknown" + remove_token: "Tem certeza de que deseja remover este token? Será necessário fazer login novamente no %{integration}." + removed: "Token de cliente OAuth removido com sucesso" + unknown_integration: "Desconhecido" token/rss: - add_button: "RSS token" - blank_description: "There is no RSS token yet. You can create one using the button below." - blank_title: "No RSS token" + add_button: "Token RSS" + blank_description: "Ainda não há um token de RSS. Você pode criar um usando o botão abaixo." + blank_title: "Sem token de RSS" title: "RSS" - table_title: "RSS tokens" - text_hint: "RSS tokens allow users to keep up with the latest changes in this OpenProject instance via an external RSS reader." - static_token_name: "RSS token" - disabled_text: "RSS tokens are not enabled by the administrator. Please contact your administrator to use this feature." + table_title: "Tokens RSS" + text_hint: "Tokens RSS permitem que os usuários acompanhem as últimas alterações desta instância do OpenProject por meio de um leitor RSS externo." + static_token_name: "Token RSS" + disabled_text: "Tokens RSS não estão habilitados pelo administrador. Entre em contato com ele para usar este recurso." storages: unknown_storage: "Armazenamento desconhecido" notifications: @@ -3059,7 +3059,7 @@ pt-BR: label_always_visible: "Sempre exibido" label_announcement: "Anúncio" label_angular: "AngularJS" - label_app_modules: "%{app_title} modules" + label_app_modules: "%{app_title} módulos" label_api_access_key: "Chave de acesso a API" label_api_access_key_created_on: "Chave de acesso a API criada %{value} atrás" label_api_access_key_type: "API" @@ -3315,7 +3315,7 @@ pt-BR: label_journal_diff: "Comparação de Descrição" label_language: "Idioma" label_languages: "Idiomas" - label_external_links: "External links" + label_external_links: "Links externos" label_locale: "Idioma e região" label_jump_to_a_project: "Saltar para um projeto..." label_keyword_plural: "Palavras-chave" @@ -4308,9 +4308,9 @@ pt-BR: setting_allowed_link_protocols: "Protocolos de link permitidos" setting_allowed_link_protocols_text_html: >- Permite que estes protocolos sejam apresentados como links nas descrições do pacote de trabalho, campos de texto e comentários. Por exemplo, %{tel_code} ou %{element_code}. Insira um protocolo por linha.
    Os protocolos %{http_code}, %{https_code} e %{mailto_code} estão sempre permitidos. - setting_capture_external_links: "Capture external links" + setting_capture_external_links: "Capturar links externos" setting_capture_external_links_text: > - When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + Quando ativado, todos os links externos em textos formatados serão redirecionados por uma página de aviso antes de sair do aplicativo. Isso ajuda a proteger os usuários de sites externos potencialmente maliciosos. setting_after_first_login_redirect_url: "Redirecionamento de primeiro login" setting_after_first_login_redirect_url_text_html: > Defina um caminho para redirecionar os usuários após o primeiro login. Se deixado em branco, eles serão redirecionados para a página inicial do tour de introdução.
    Exemplo: /my/page @@ -4324,16 +4324,16 @@ pt-BR: Se o CORS estiver habilitado, essas são as origens que têm permissão para acessar a API OpenProject.
    Por favor, verifique a documentação na header da Origin sobre como especificar os valores esperados. setting_apiv3_write_readonly_attributes: "Acesso de gravação a atributos somente leitura" setting_apiv3_write_readonly_attributes_instructions: > - If enabled, the API will allow administrators to write static read-only attributes during creation, such as createdAt and author. + Se ativada, a API permitirá que os administradores gravem atributos estáticos de somente leitura durante a criação, como createdAt e author. setting_apiv3_write_readonly_attributes_warning: > - This setting has a use-case for e.g., importing data, but allows administrators to impersonate the creation of items as other users. All creation requests are being logged however with the true author. + Esta configuração tem casos de uso, por exemplo, para importação de dados, mas permite que administradores criem itens em nome de outros usuários. Todas as solicitações de criação, no entanto, são registradas com o autor verdadeiro. setting_apiv3_write_readonly_attributes_additional: > - For more information on attributes and supported resources, please see the %{api_documentation_link}. + Para obter mais informações sobre atributos e recursos suportados, consulte %{api_documentation_link}. setting_apiv3_max_page_size: "Tamanho máximo da página de API" setting_apiv3_max_page_size_instructions: > - Set the maximum page size the API will respond with. It will not be possible to perform API requests that return more values on a single page. + Defina o tamanho máximo de página que a API irá retornar. Não será possível realizar solicitações à API que retornem mais valores em uma única página. setting_apiv3_max_page_size_warning: > - Please only change this value if you are sure why you need it. Setting to a high value will result in significant performance impacts, while a value lower than the per page options will cause errors in paginated views. + Altere este valor apenas se tiver certeza de que é necessário. Configurar um valor alto pode gerar impacto significativo no desempenho, enquanto um valor menor do que as opções por página causará erros em visualizações paginadas. setting_apiv3_docs: "Documentação" setting_apiv3_docs_enabled: "Habilitar página de documentos" setting_apiv3_docs_enabled_instructions_html: > @@ -4518,7 +4518,7 @@ pt-BR: omniauth_direct_login_hint_html: > Se esta opção estiver ativada, as solicitações de login serão redirecionadas para o provedor omniauth configurado. O menu suspenso de login e a página de login serão desativados.
    Obs.: A menos que você também desative os logins por senha, com esta opção ativada, os usuários ainda poderão fazer login internamente acessando a página de login em %{internal_path}. remapping_existing_users_hint: > - If enabled, allows any configured identity provider to login existing users based on their username, even if the user never signed in through that provider before. This can be useful when migrating the OpenProject instance to a new SSO provider, but is not recommended when using a provider that is not trusted by all users of your instance. + Se ativado, permite que qualquer provedor de identidade configurado faça login de usuários existentes com base em seu nome de usuário, mesmo que o usuário nunca tenha acessado através desse provedor antes. Isso pode ser útil ao migrar a instância do OpenProject para um novo provedor SSO, mas não é recomendado ao usar um provedor que não seja confiável para todos os usuários da sua instância. attachments: whitelist_text_html: > Defina uma lista de extensões de arquivo válidas e/ou tipos MIME para arquivos carregados.
    Insira as extensões de arquivo (e.x., %{ext_example}) ou tipos de mime (e.x., %{mime_example}).
    Deixe em branco para permitir que qualquer tipo de arquivo seja carregado. Vários valores permitidos (uma linha para cada valor). @@ -4624,7 +4624,7 @@ pt-BR: project_mandate: "Mandato do projeto" submission: description_template: > - **This work package was automatically created upon completion of the %{wizard_name} workflow.** A PDF artifact containing all submitted information has been generated and attached to this work package for reference and audit purposes. If you need to update or re-run the initiation steps, you can reopen the wizard at any time by using the link below: + **Este pacote de trabalho foi criado automaticamente ao concluir o fluxo de trabalho %{wizard_name}.** Um artefato em PDF contendo todas as informações enviadas foi gerado e anexado a este pacote de trabalho para referência e auditoria. Se for necessário atualizar ou refazer as etapas de início, você pode reabrir o assistente a qualquer momento usando o link abaixo: description: "Quando um usuário envia uma solicitação de início de projeto, um novo pacote de trabalho será criado com o artefato da solicitação anexado como arquivo PDF. As configurações abaixo definem o tipo, o status e o responsável por este novo pacote de trabalho." work_package_type: "Tipo de pacote de trabalho" work_package_type_caption: "O tipo de pacote de trabalho que deve ser usado para armazenar o artefato concluído." @@ -4883,8 +4883,8 @@ pt-BR: other: "bloqueado temporariamente (%{count} tentativas de login inválidas)" confirm_status_change: "Você está prestes a mudar a situação de '%{name}'. Tem certeza que deseja continuar?" deleted: "Usuário excluído" - error_status_change_self: "You cannot change your own user status." - error_admin_change_on_non_admin: "Only administrators can change the status of administrator users." + error_status_change_self: "Você não pode alterar o seu próprio status de usuário." + error_admin_change_on_non_admin: "Somente administradores podem alterar o status de usuários administradores." error_status_change_failed: "A alteração da situação do usuário falhou devido aos seguintes erros: %{errors}" invite: Convidar usuário através de e-mail invited: convidado @@ -5289,7 +5289,7 @@ pt-BR: hashed_token: display_value_placeholder: "***" external_link_warning: - title: "Leaving OpenProject" - warning_message: "You are about to leave OpenProject and visit an external website. Please be aware that external websites are not under our control and may have different privacy and security policies." - continue_message: "Are you sure you want to proceed to the following external link?" - continue_button: "Continue to external website" + title: "Saindo do OpenProject" + warning_message: "Você está prestes a sair do OpenProject e acessar um site externo. Lembre-se de que sites externos não estão sob nosso controle e podem ter políticas de privacidade e segurança diferentes." + continue_message: "Tem certeza de que deseja acessar o seguinte link externo?" + continue_button: "Continuar para o site externo" diff --git a/config/locales/crowdin/tr.yml b/config/locales/crowdin/tr.yml index c4ffdfe5f5d..0c15c0de7bf 100644 --- a/config/locales/crowdin/tr.yml +++ b/config/locales/crowdin/tr.yml @@ -87,7 +87,7 @@ tr: token_placeholder: "Enterprise sürümü destek anahtarınızı buraya yapıştırın" add_token: "Bir Enterprise sürümü destek belirteci yükleyin" replace_token: "Geçerli destek anahtarını değiştirin" - order: "Enterprise on-premises edition sipariş edin" + order: "Kurumsal on-premises sürümü sipariş edin" paste: "Enterprise sürümü destek belirtecinizi yapıştırın" required_for_feature: "Bu eklenti, yalnızca etkin bir Enterprise sürümü destek belirteci ile kullanılabilir." enterprise_link: "Daha fazla bilgi için burayı tıklatın." @@ -572,7 +572,7 @@ tr: members: excluded_roles_label: "Roles to exclude when template is applied" excluded_roles_caption: > - When creating a new project from this template, the roles selected above will be omitted. This allows you to select which members will be excluded based on their project roles. Users can then access the template for viewing purposes without being granted access to new projects created from it. + Bu şablondan yeni bir proje oluştururken, yukarıda seçilen roller atlanacaktır. Bu, proje rollerine göre hangi üyelerin hariç tutulacağını seçmenize olanak tanır. Kullanıcılar daha sonra şablona görüntüleme amacıyla erişebilir, ancak şablondan oluşturulan yeni projelere erişim izni alamazlar. actions: label_enable_all: "Tümünü etkinleştir" label_disable_all: "Tümünü devre dışı bırak" @@ -1436,7 +1436,7 @@ tr: force_dark_theme_contrast_caption: "Otomatik renk modu seçildiğinde Koyu modun yüksek kontrastlı versiyonunu kullanır." theme: "Renk modu" time_zone: "Zaman dilimi" - mode_guideline: "Some modes will overwrite custom theme colors for accessibility and legibility. Please select Light mode for full custom theme support." + mode_guideline: "Bazı modlar, erişilebilirlik ve okunabilirlik için özel tema renklerinin üzerine yazacaktır. Tam özel tema desteği için lütfen Aydınlık modunu seçin." daily_reminders: "Günlük hatırlatmalar" workdays: "İş Günleri" users/invitation/form_model: @@ -2464,7 +2464,7 @@ tr: edit_attribute_groups: Özellik Grubunu Düzenleme gantt_pdf_export: Gantt PDF Dışa Aktarma ldap_groups: LDAP kullanıcıları ve grup senkronizasyonu - mcp_server: MCP Server + mcp_server: nextcloud_sso: Single Sign-On for Nextcloud Storage one_drive_sharepoint_file_storage: OneDrive/SharePoint Dosya Depolama placeholder_users: Yer Tutucu Kullanıcılar @@ -2555,7 +2555,7 @@ tr: virus_scanning: description: "OpenProject'te yüklenen dosyaların diğer kullanıcılar tarafından erişilmeden önce virüslere karşı tarandığından emin olun." project_creation_wizard: - description: "Generate a step-by-step wizard to help project managers fill out a project initiation request." + description: "Proje yöneticilerinin proje başlatma talebini doldurmasına yardımcı olacak adım adım bir sihirbaz oluşturun." placeholder_users: title: Yer tutucu kullanıcı description: > @@ -4153,7 +4153,7 @@ tr: heading: "Bu projeyi kalıcı olarak silmek istiyor musunuz?" confirmation_message_for_subprojects_html: zero: > - You are about to permanently delete all data relating to project %{name}. + Proje ile ilgili tüm verileri kalıcı olarak silmek üzeresiniz %{name}. one: > You are about to permanently delete all data relating to project %{name} and this subproject: other: > diff --git a/config/locales/crowdin/uk.yml b/config/locales/crowdin/uk.yml index 7e5277c36d0..a0333029c88 100644 --- a/config/locales/crowdin/uk.yml +++ b/config/locales/crowdin/uk.yml @@ -113,10 +113,10 @@ uk: index: description: "Протокол контексту моделі дає змогу агентам ШІ надавати своїм користувачам інструменти й ресурси, доступні в цьому екземплярі OpenProject." resources_heading: "Ресурси" - resources_description: "OpenProject реалізує наведені нижче інструменти. Кожен із них можна ввімкнути, перейменувати й описати як завгодно. Щоб дізнатися більше, ознайомтеся з [документацією на ресурсах MCP] (docs_url)." + resources_description: "OpenProject реалізує наведені нижче інструменти. Кожен із них можна ввімкнути, перейменувати й описати як завгодно. Щоб дізнатися більше, ознайомтеся з [документацією щодо ресурсів MCP] (docs_url)." resources_submit: "Оновити ресурси" tools_heading: "Інструменти" - tools_description: "OpenProject реалізує наведені нижче інструменти. Кожен із них можна ввімкнути, перейменувати й описати як завгодно. Щоб дізнатися більше, ознайомтеся з [документацією з інструментів MCP] (docs_url)." + tools_description: "OpenProject реалізує наведені нижче інструменти. Кожен із них можна ввімкнути, перейменувати й описати як завгодно. Щоб дізнатися більше, ознайомтеся з [документацією щодо інструментів MCP] (docs_url)." tools_submit: "Оновити інструменти" multi_update: success: "Конфігурації MCP оновлено." @@ -2951,7 +2951,7 @@ uk: new_features_list: line_0: Автоматизований запуск проєктів (надбудова Enterprise). line_1: "Наради: додавайте нові або наявні пакети робіт як підсумки." - line_2: "Наради: показуйте відповіді учасників у підписках iCal." + line_2: "Meetings: show iCal responses in OpenProject." line_3: "Повторювані наради: копіюйте пункти порядку денного в наступну нараду." line_4: "Випуск Community: виділення атрибутів." line_5: Попередження перед відкриттям зовнішніх посилань у контенті, створеному користувачем (надбудова Enterprise). @@ -3033,7 +3033,7 @@ uk: label: "Додати…" my_account: access_tokens: - description: "Маркери постачальника послуг випускаються в OpenProject, що дає змогу іншим програмам отримувати до них доступ. Клієнтські маркери випускаються іншими додатками, що дає змогу OpenProject отримати до них доступ." + description: "Маркери постачальника послуг випускаються в OpenProject, що дає змогу іншим додаткам отримувати до них доступ. Клієнтські маркери випускаються іншими додатками, що дає змогу OpenProject отримати до них доступ." no_results: title: "Немає маркерів доступу для відображення" description: "Всі вони були відключені. Їх можна повторно ввімкнути в меню адміністрування." @@ -3091,7 +3091,7 @@ uk: table_title: "Маркери RSS" text_hint: "Маркери RSS дають змогу користувачам зберігати останні зміни в цьому екземплярі OpenProject через зовнішній RSS-агрегатор." static_token_name: "Маркер RSS" - disabled_text: "Маркери API не дозволено адміністратором. Зверніться до нього, якщо вам потрібна ця функція." + disabled_text: "Маркери API не ввімкнено адміністратором. Зверніться до нього, якщо вам потрібна ця функція." storages: unknown_storage: "Невідоме сховище" notifications: @@ -4408,7 +4408,7 @@ uk: Дозвольте відображення цих протоколів у вигляді посилань в описах пакетів робіт, довгих текстових полях і коментарях. Наприклад, %{tel_code} або %{element_code}. Вводьте по одному протоколу в рядок.
    Протоколи %{http_code}, %{https_code} і %{mailto_code} завжди дозволені. setting_capture_external_links: "Захоплення зовнішніх посилань" setting_capture_external_links_text: > - Якщо ввімкнено, усі зовнішні посилання у відформатованому тексті переспрямовуватимуть на попереджувальну сторінку перед тим, як вийти з програми. Це допомагає захистити користувачів від потенційно шкідливих зовнішніх вебсайтів. + Якщо ввімкнено, усі зовнішні посилання у відформатованому тексті переспрямовуватимуть на попереджувальну сторінку перед переходом із додатка. Це допомагає захистити користувачів від потенційно шкідливих зовнішніх вебсайтів. setting_after_first_login_redirect_url: "Переспрямування після першого входу" setting_after_first_login_redirect_url_text_html: > Задайте шлях для переспрямування користувачів після першого входу. Якщо не задано, користувачі переспрямовуються на головну сторінку з ознайомленням.
    Наприклад: /my/page @@ -4422,16 +4422,16 @@ uk: Якщо CORS увімкнено, це джерела, які можуть отримувати доступ до OpenProject API.
    Щоб дізнатися, як указати очікувані значення, ознайомтеся з Документацією щодо заголовка джерела. setting_apiv3_write_readonly_attributes: "Дозвіл для записування атрибутів лише для читання" setting_apiv3_write_readonly_attributes_instructions: > - Якщо ввімкнено, адміністраторам зможуть за допомогою API записувати статичні атрибути тільки для читання під час створення, такі як createdAt та author. + Якщо ввімкнено, адміністратори зможуть за допомогою API записувати статичні атрибути тільки для читання під час створення, такі як createdAt та author. setting_apiv3_write_readonly_attributes_warning: > - Це налаштування використовується, наприклад, для імпорту даних, але дає змогу адміністраторам створювати елементів від імені інших користувачів. Усі запити на створення реєструються, однак, із зазначенням справжнього автора. + Це налаштування використовується, наприклад, для імпорту даних, але дає змогу адміністраторам створювати елементи від імені інших користувачів. Однак усі запити на створення реєструються із зазначенням справжнього автора. setting_apiv3_write_readonly_attributes_additional: > Щоб дізнатися більше про атрибути й підтримувані ресурси, відвідайте %{api_documentation_link}. setting_apiv3_max_page_size: "Максимальний розмір сторінки API" setting_apiv3_max_page_size_instructions: > Установіть максимальний розмір сторінки, яку повертатиме API. Виконувати запити до API, які повертають більше значень на одній сторінці, буде неможливо. setting_apiv3_max_page_size_warning: > - Змінюйте це значення, лише якщо впевнені, навіщо це вам потрібно. Встановлення високого значення призведе до суттєвого впливу на продуктивність, тоді як значення, нижче за параметри на сторінки, призведе до помилок у посторінкових поданнях. + Змінюйте це значення, лише якщо впевнені, навіщо це вам потрібно. Встановлення високого значення призведе до суттєвого впливу на продуктивність, тоді як значення, нижче за параметри кожної сторінки, призведе до помилок у посторінкових поданнях. setting_apiv3_docs: "Документація" setting_apiv3_docs_enabled: "Увімкнути сторінку документів" setting_apiv3_docs_enabled_instructions_html: > @@ -4722,7 +4722,7 @@ uk: project_mandate: "Мандат проєкту" submission: description_template: > - **Цей пакет робіт автоматично створено після завершення робочого процесу «%{wizard_name}».** Артефакт у форматі PDF, що містить усю подану інформацію, було створено й додано в цей пакет робіт для довідки й аудиту. Якщо знадобиться оновити дані або повторити кроки ініціювання, можна будь-коли знову відкрити майстер, скориставшись посиланням, наведеним нижче: + **Цей пакет робіт автоматично створено після завершення робочого процесу «%{wizard_name}».** Артефакт у форматі PDF, що містить усю подану інформацію, було створено й додано в цей пакет робіт для довідки й аудиту. Якщо знадобиться оновити дані або повторити кроки із запуску, можна будь-коли знову відкрити майстер, скориставшись посиланням, наведеним нижче: description: "Коли користувач подає запит на ініціювання проєкту, створюється новий пакет робіт з артефактом запиту, долученим як файл PDF. Наведені нижче налаштування визначають тип, статус і виконавця нового пакета робіт." work_package_type: "Тип пакета робіт" work_package_type_caption: "Тип пакета робіт, який слід використовувати для зберігання завершеного артефакту." @@ -5392,6 +5392,6 @@ uk: display_value_placeholder: "***" external_link_warning: title: "Вихід з OpenProject" - warning_message: "Ви збираєтеся перейти з OpenProject на зовнішній вебсайт. Зауважте, що ми не контролюємо зовнішні вебсайти, і вони можуть мати іншу політику конфіденційності й безпеки." + warning_message: "Ви збираєтеся перейти з OpenProject на зовнішній вебсайт. Зауважте, що ми не контролюємо зовнішні вебсайти, і вони можуть мати інші політики конфіденційності й безпеки." continue_message: "Справді перейти за зовнішнім посиланням нижче?" continue_button: "Перейти на зовнішній вебсайт" diff --git a/config/locales/crowdin/vi.yml b/config/locales/crowdin/vi.yml index 1c56e1b442d..702e0ebe213 100644 --- a/config/locales/crowdin/vi.yml +++ b/config/locales/crowdin/vi.yml @@ -2002,7 +2002,7 @@ vi: body: "cơ thể" blocks_ids: "ID của các work package bị chặn" category: "thể loại" - comment: "bình luận" + comment: "Nhận xét" comments: "bình luận" content: "Nội dung" color: "màu sắc" @@ -2808,7 +2808,7 @@ vi: new_features_list: line_0: Khởi tạo dự án tự động (Phần mở rộng doanh nghiệp). line_1: "Họp: Thêm các gói công việc mới hoặc hiện có làm kết quả." - line_2: "Họp: Hiển thị phản hồi của người tham gia trong các đăng ký iCal." + line_2: "Họp: Hiển thị phản hồi iCal trong OpenProject." line_3: "Cuộc họp định kỳ: Sao chép các mục trong chương trình nghị sự sang lần họp tiếp theo." line_4: "Phát hành cho cộng đồng: Tính năng làm nổi bật thuộc tính." line_5: Cảnh báo trước khi mở các liên kết bên ngoài trong nội dung do người dùng cung cấp (Phần mở rộng doanh nghiệp). @@ -3011,7 +3011,7 @@ vi: label_always_visible: "Luôn luôn hiển thị" label_announcement: "Thông báo" label_angular: "AngularJS" - label_app_modules: "%{app_title} modules" + label_app_modules: "%{app_title} các mô-đun" label_api_access_key: "Khoá truy cập API" label_api_access_key_created_on: "Khoá truy cập API đựơc tạo cách đây %{value}" label_api_access_key_type: "API" @@ -3582,7 +3582,7 @@ vi: label_used_by: "Được dùng bởi" label_used_by_types: "Được sử dụng bởi các loại" label_used_in_projects: "Được sử dụng trong các dự án" - label_user: "người dùng" + label_user: "Người dùng" label_user_and_permission: "Người dùng và quyền" label_user_named: "Người dùng %{name}" label_user_activity: "%{value} hoạt động" diff --git a/config/locales/crowdin/zh-CN.yml b/config/locales/crowdin/zh-CN.yml index 7e274e51038..f23366ad1f8 100644 --- a/config/locales/crowdin/zh-CN.yml +++ b/config/locales/crowdin/zh-CN.yml @@ -2806,7 +2806,7 @@ zh-CN: new_features_list: line_0: 自动化项目启动(企业版附加组件)。 line_1: "会议:添加新工作包或现有工作包作为成果。" - line_2: "会议:在 iCal 订阅中显示参加者的回复。" + line_2: "Meetings: show iCal responses in OpenProject." line_3: "定期会议:将议程条目复制到下一事件。" line_4: "发布到社区:特性高亮显示。" line_5: 在打开用户提供内容中的外部链接前发出警告(企业版附加组件)。 diff --git a/config/locales/crowdin/zh-TW.yml b/config/locales/crowdin/zh-TW.yml index 640b6f19558..34769acac40 100644 --- a/config/locales/crowdin/zh-TW.yml +++ b/config/locales/crowdin/zh-TW.yml @@ -2806,7 +2806,7 @@ zh-TW: new_features_list: line_0: 自動化專案啟動(企業附加元件)。 line_1: "會議:添加新的或現有的工作套件作為成果。" - line_2: "Meetings: show iCal responses in OpenProject." + line_2: "會議:在 OpenProject 中顯示 iCal 回應。" line_3: "重複性會議:將議程項目重複至下次會議。" line_4: "發佈到社群:高亮顯示屬性。" line_5: 在使用者提供的內容中開啟外部連結前警告(企業附加元件)。 @@ -3009,7 +3009,7 @@ zh-TW: label_always_visible: "總是顯示" label_announcement: "公告" label_angular: "AngularJS" - label_app_modules: "%{app_title} modules" + label_app_modules: "%{app_title} 模組" label_api_access_key: "API金鑰" label_api_access_key_created_on: "API金鑰在 %{value} 以前被建立" label_api_access_key_type: "API" diff --git a/modules/auth_saml/config/locales/crowdin/tr.yml b/modules/auth_saml/config/locales/crowdin/tr.yml index 4e39d034a95..b03773ac625 100644 --- a/modules/auth_saml/config/locales/crowdin/tr.yml +++ b/modules/auth_saml/config/locales/crowdin/tr.yml @@ -56,7 +56,7 @@ tr: label_mapping: İlişkilendirme label_requested_attribute_for: "%{attribute} için nitelik talebi" no_results_table: Henüz hiçbir SAML kimlik sağlayıcısı tanımlanmamış. - notice_created: A new SAML identity provider was successfully created. + notice_created: Yeni bir SAML kimlik sağlayıcısı başarıyla oluşturuldu. plural: SAML kimlik sağlayıcıları singular: SAML kimlik sağlayıcısı requested_attributes: Talep edilen nitelikler diff --git a/modules/backlogs/config/locales/crowdin/ja.yml b/modules/backlogs/config/locales/crowdin/ja.yml index bd238d8474c..c5589093ff4 100644 --- a/modules/backlogs/config/locales/crowdin/ja.yml +++ b/modules/backlogs/config/locales/crowdin/ja.yml @@ -41,7 +41,7 @@ ja: sprint: cannot_end_before_it_starts: "スプリントは開始する前に終了できません。" attributes: - task_type: "Task type" + task_type: "タスクのタイプ" backlogs: add_new_story: "新しいストーリー" any: "全て" @@ -58,7 +58,7 @@ ja: impediment: "障害事項" label_versions_default_fold_state: "バージョンを折り畳んで表示" caption_versions_default_fold_state: "バックログを表示する場合、デフォルトではバージョンは展開されません。各バージョンは手動で展開する必要があります。" - work_package_is_closed: "作業パッケージが終了するには" + work_package_is_closed: "ワークパッケージが終了するには" label_is_done_status: "ステータス%{status_name}は完了として扱う" no_burndown_data: "バーンダウンデータがありません。スプリントの開始日と終了日を設定する必要があります。" points: "ポイント" @@ -67,8 +67,8 @@ ja: properties: "プロパティ" rebuild: "再構築" rebuild_positions: "位置情報を再構築" - remaining_hours: "残りの作業" - remaining_hours_ideal: "残りの作業(計画)" + remaining_hours: "残時間" + remaining_hours_ideal: "残時間(計画)" show_burndown_chart: "バーンダウングラフ" story: "ストーリー" story_points: "ストーリーポイント" @@ -129,10 +129,10 @@ ja: label_points_burn_up: "アップ" label_product_backlog: "プロダクトバックログ" label_select_all: "全てを選択" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" + label_select_type: "タイプを選択" + label_select_types: "タイプを選択" + label_selected_type: "タイプを選択" + label_selected_types: "タイプを選択" label_sprint_backlog: "スプリントバックログ" label_sprint_cards: "カードをエクスポート" label_sprint_impediments: "スプリント障害事項" @@ -160,7 +160,7 @@ ja: rb_label_copy_tasks_none: "なし" rb_label_copy_tasks_open: "開く" rb_label_link_to_original: "元のストーリーへのリンクを含める" - remaining_hours: "残りの作業" + remaining_hours: "残時間" required_burn_rate_hours: "必要なバーンレート(時間)" required_burn_rate_points: "必要なバーンレート(ポイント)" todo_work_package_description: "%{summary}: %{url}\n%{description}" @@ -172,4 +172,4 @@ ja: setting_plugin_openproject_backlogs_story_types_caption: | Types treated as backlog stories (e.g., Feature, User story). Must differ from task type. setting_plugin_openproject_backlogs_task_type_caption: | - Type used for story tasks. Must differ from story types. + ストーリータスクに使用されるタイプ。ストーリータイプと異なる必要があります。 diff --git a/modules/bim/config/locales/crowdin/ja.seeders.yml b/modules/bim/config/locales/crowdin/ja.seeders.yml index 5ca20a92432..189d67851d3 100644 --- a/modules/bim/config/locales/crowdin/ja.seeders.yml +++ b/modules/bim/config/locales/crowdin/ja.seeders.yml @@ -185,7 +185,7 @@ ja: 1. プロジェクトに新しいメンバーを招待する: _ → プロジェクトナビゲーションの [メンバー]({{opSetting:base_url}}/projects/demo-planning-constructing-project/members) に移動します。 - 2. プロジェクト内の作業を見る:_ → プロジェクトナビゲーションの[作業パッケージ]({{opSetting:base_url}}/projects/demo-planning-constructing-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22bcfIssueAssociated%22%2C%22o%22%3A%22%3D%22%2C%22v%22%3A%5B%22f%22%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D) を開きます。 + 2. プロジェクト内の作業を見る:_ → プロジェクトナビゲーションの[ワークパッケージ]({{opSetting:base_url}}/projects/demo-planning-constructing-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22bcfIssueAssociated%22%2C%22o%22%3A%22%3D%22%2C%22v%22%3A%5B%22f%22%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D) を開きます。 3. 新しいワークパッケージを作成する:_ → [ワークパッケージ → 作成]({{opSetting:base_url}}/projects/demo-planning-constructing-project/work_packages/new?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22bcfIssueAssociated%22%2C%22o%22%3A%22%3D%22%2C%22v%22%3A%5B%22f%22%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D&type=11). 4. ガントチャートの作成と更新:_ → [ガントチャート]({{opSetting:base_url}}/projects/demo-planning-constructing-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22assignee%22%2C%22responsible%22%5D%2C%22tv%22%3Atrue%2C%22tzl%22%3A%22weeks%22%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D) プロジェクトナビゲーションにある。 5. プロジェクト設定 → モジュール]({{opSetting:base_url}}/projects/demo-planning-constructing-project/settings/modules)に進みます。 @@ -224,7 +224,7 @@ ja: ## 概要 * 顧客とワークショップを行い、顧客のニーズを特定する - * 各ニーズは、対応する作業パッケージとともにタスクを表す + * 各ニーズは、対応するワークパッケージとともにタスクを表す * 見積もりコストと時間枠を算出する item_1: subject: 結果の概要 diff --git a/modules/bim/config/locales/crowdin/ja.yml b/modules/bim/config/locales/crowdin/ja.yml index e8dae20ba54..4ff56d54b8d 100644 --- a/modules/bim/config/locales/crowdin/ja.yml +++ b/modules/bim/config/locales/crowdin/ja.yml @@ -140,4 +140,4 @@ ja: permission_manage_ifc_models: "IFCモデルのインポートと管理" extraction: available: - ifc_convert: "IFC変換パイプランは利用可能です" + ifc_convert: "IFC変換パイプラインは利用可能です" diff --git a/modules/costs/config/locales/crowdin/ja.yml b/modules/costs/config/locales/crowdin/ja.yml index 015103d844e..a029efa7b97 100644 --- a/modules/costs/config/locales/crowdin/ja.yml +++ b/modules/costs/config/locales/crowdin/ja.yml @@ -39,12 +39,12 @@ ja: unit_plural: "単位名" default: "デフォルトのコストタイプ" work_package: - costs_by_type: "使用済単価" + costs_by_type: "消費した予算" labor_costs: "作業員コスト" material_costs: "単価" overall_costs: "集計コスト" spent_costs: "使用済コスト" - spent_units: "使用済単価" + spent_units: "消費した予算" rate: rate: "原価率" user: diff --git a/modules/costs/config/locales/crowdin/js-ja.yml b/modules/costs/config/locales/crowdin/js-ja.yml index db8633ed00b..e0c4861fe5f 100644 --- a/modules/costs/config/locales/crowdin/js-ja.yml +++ b/modules/costs/config/locales/crowdin/js-ja.yml @@ -29,7 +29,7 @@ ja: costs: "コスト" properties: overallCosts: "集計コスト" - spentUnits: "使用済単価" + spentUnits: "消費した予算" button_log_costs: "単価を記録" label_hour: " 時間" label_hours: "時間" diff --git a/modules/costs/config/locales/crowdin/tr.yml b/modules/costs/config/locales/crowdin/tr.yml index 7c4f2b3295c..e9c15aa795d 100644 --- a/modules/costs/config/locales/crowdin/tr.yml +++ b/modules/costs/config/locales/crowdin/tr.yml @@ -209,7 +209,7 @@ tr: setting_enforce_tracking_start_and_end_times: "Başlangıç ve bitiş saatleri gerektir" setting_enforce_without_allow: "Başlangıç ve bitiş saatlerini zorunlu tutmak, bunlara izin vermeden mümkün değildir" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." - setting_enforce_tracking_start_and_end_times_caption: "Makes entering start and finish times mandatory when logging time." + setting_enforce_tracking_start_and_end_times_caption: "Zaman kaydı yaparken başlangıç ve bitiş zamanlarının girilmesini zorunlu hale getirir." text_assign_time_and_cost_entries_to_project: "Raporlanan saatleri ve maliyetleri projeye tahsis edin" text_destroy_cost_entries_question: "%{cost_entries} silmek üzere olduğunuz iş paketleri hakkında rapor edildi. Ne yapmak istiyorsun ?" text_destroy_time_and_cost_entries: "Raporlanan saat ve masrafları sil" diff --git a/modules/documents/config/locales/crowdin/es.seeders.yml b/modules/documents/config/locales/crowdin/es.seeders.yml index 097b40e6994..5cb11bb4478 100644 --- a/modules/documents/config/locales/crowdin/es.seeders.yml +++ b/modules/documents/config/locales/crowdin/es.seeders.yml @@ -15,6 +15,6 @@ es: item_3: name: Especificación item_4: - name: Reporte + name: Informe item_5: name: Documentación diff --git a/modules/documents/config/locales/crowdin/es.yml b/modules/documents/config/locales/crowdin/es.yml index 65b2ce48cbd..93207b0b3fe 100644 --- a/modules/documents/config/locales/crowdin/es.yml +++ b/modules/documents/config/locales/crowdin/es.yml @@ -72,7 +72,7 @@ es: action: Intentar otra vez connection_recovery_notice: description: "La conexión en tiempo real ha sido restaurada." - tabs: "Document tabs" + tabs: "Pestañas del documento" index_page: name: "Nombre" type: "Tipo" diff --git a/modules/documents/config/locales/crowdin/ja.yml b/modules/documents/config/locales/crowdin/ja.yml index c6e49a13d43..192d1833de4 100644 --- a/modules/documents/config/locales/crowdin/ja.yml +++ b/modules/documents/config/locales/crowdin/ja.yml @@ -27,13 +27,13 @@ ja: errors: models: document_type: - one_or_more_required: "Cannot delete the last document type" + one_or_more_required: "最後のドキュメントタイプを削除することはできません" models: document: "ドキュメント" - documents: "Documents" + documents: "ドキュメント" attributes: document: - content_binary: "Content binary" + content_binary: "コンテンツのバイナリ" title: "タイトル" activity: filter: @@ -43,38 +43,38 @@ ja: enumeration_doc_categories: "ドキュメントのカテゴリ" documents: page_header: - heading: "All documents" + heading: "すべてのドキュメント" action_menu: - document_actions: "Document actions" - edit_title: "Edit title" + document_actions: "ドキュメントのアクション" + edit_title: "タイトルを編集" subheader: filter: - label: "Document name filter" - placeholder: "Type here to search document names" + label: "ドキュメント名フィルター" + placeholder: "ここに入力してドキュメントを検索" documents_list_blank_slate: - heading: "There are no documents yet" - description: "There are no documents in this view. You can click the button below to add one." + heading: "ドキュメントがありません" + description: "このビューにはドキュメントがありません。下のボタンをクリックして追加することができます。" document_categories_deprecation_notice: - heading: File categories are now called 'Document types' + heading: ファイルカテゴリーは「ドキュメントタイプ」に変更されました。 description: |- - Your existing file categories have been converted to document types with the introduction of the new Documents module. - All existing documents have also been migrated to these new types. - primary_action: Configure document types - secondary_action: Learn more about the Documents module - document_type_actions: "Document type actions" + 新しいDocumentsモジュールの導入に伴い、既存のファイルカテゴリーがドキュメントタイプに変換されました。 + すべての既存のドキュメントもこれらの新しいタイプに移行されました。 + primary_action: ドキュメントタイプを設定 + secondary_action: ドキュメントモジュールの詳細 + document_type_actions: "ドキュメントタイプのアクション" text_collaboration_disabled_notice: description: |- - Unable to open document because real-time text collaboration is disabled. - Please contact your administrator to enable real-time text collaboration if you want to access this document. + リアルタイムテキストコラボレーションが無効になっているため、ドキュメントを開くことができません。 + この文書にアクセスしたい場合は、管理者に連絡してリアルタイムテキスト連携を有効にしてください。 show_edit_view: connection_error_notice: description: |- - Unable to open document because the real-time text collaboration server is unreachable. - Please contact the administrator if the problem persists. - action: Try again + リアルタイムテキストコラボレーションサーバーにアクセスできないため、ドキュメントを開くことができません。 + 問題が解決しない場合は、管理者にお問い合わせください。 + action: もう一度試してください connection_recovery_notice: - description: "The connection to the real-time text collaboration server has been restored." - tabs: "Document tabs" + description: "リアルタイムテキストコラボレーションサーバーへの接続が回復しました。" + tabs: "ドキュメントタブ" index_page: name: "名称" type: "タイプ" diff --git a/modules/documents/config/locales/crowdin/tr.seeders.yml b/modules/documents/config/locales/crowdin/tr.seeders.yml index c8ae575fd8d..e9ca364a907 100644 --- a/modules/documents/config/locales/crowdin/tr.seeders.yml +++ b/modules/documents/config/locales/crowdin/tr.seeders.yml @@ -17,4 +17,4 @@ tr: item_4: name: Report item_5: - name: Documentation + name: Belgeleme diff --git a/modules/documents/config/locales/crowdin/tr.yml b/modules/documents/config/locales/crowdin/tr.yml index b3cbc6b8f7d..7f244d338d3 100644 --- a/modules/documents/config/locales/crowdin/tr.yml +++ b/modules/documents/config/locales/crowdin/tr.yml @@ -61,7 +61,7 @@ tr: All existing documents have also been migrated to these new types. primary_action: Configure document types secondary_action: Learn more about the Documents module - document_type_actions: "Document type actions" + document_type_actions: "Belge türü eylemleri" text_collaboration_disabled_notice: description: |- Unable to open document because real-time text collaboration is disabled. @@ -88,7 +88,7 @@ tr: active_editors: "Active editors" active_editors_count: one: "1 active editor" - other: "%{count} active editors" + other: "%{count} aktif editör" label_attachment_author: "Ek yazar" label_categories: "Kategoriler" new_category: "Yeni kategori" @@ -149,7 +149,7 @@ tr: label_document_title: "Başlık" label_document_description: "Açıklama" label_document_category: "Kategori" - label_document_type: "Type" + label_document_type: "Tür" permission_manage_documents: "Belgeleri yönetme" permission_view_documents: "Belgeleri görüntüleme" project_module_documents: "Belgeler" diff --git a/modules/grids/config/locales/crowdin/js-tr.yml b/modules/grids/config/locales/crowdin/js-tr.yml index f5a032816a7..7e94bf020cb 100644 --- a/modules/grids/config/locales/crowdin/js-tr.yml +++ b/modules/grids/config/locales/crowdin/js-tr.yml @@ -19,7 +19,7 @@ tr: title: 'Description' no_results: "Henüz bir açıklama yazılmadı. Biri 'Proje ayarları' içinde sağlanabilir." project_status: - title: 'Status' + title: 'Durum' not_started: 'Başlatılmadı' on_track: 'Takipte' off_track: 'Hedeften uzaklaşmış' @@ -28,7 +28,7 @@ tr: finished: 'Tamamlandı' discontinued: 'Durduruldu' project_status_beta: - title: 'Status (BETA)' + title: 'Durum (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/tr.yml b/modules/grids/config/locales/crowdin/tr.yml index 4d8e0bf1ad6..b54e8c038b7 100644 --- a/modules/grids/config/locales/crowdin/tr.yml +++ b/modules/grids/config/locales/crowdin/tr.yml @@ -6,7 +6,7 @@ tr: not_available: "This widget is not available." subitems: title: "Alt öğeler" - no_results: "Görünürde hiç çocuk yok." + no_results: "Görüntülenebilir alt öğe yok." view_all_subitems: "Tüm alt öğeleri görüntüle" button_text: "Alt öğe" members: @@ -16,7 +16,7 @@ tr: show_members_count: "Show all %{count} members" x_more: one: "and one more member." - other: "and %{count} more members." + other: "ve %{count} üye daha." news: no_results: "Rapor için yeni birşey bulunamadı." activerecord: diff --git a/modules/job_status/config/locales/crowdin/tr.yml b/modules/job_status/config/locales/crowdin/tr.yml index 42c70cd7ce6..ca87c14a4ef 100644 --- a/modules/job_status/config/locales/crowdin/tr.yml +++ b/modules/job_status/config/locales/crowdin/tr.yml @@ -2,7 +2,7 @@ tr: label_job_status_plural: "İş durumu" plugin_openproject_job_status: name: "OpenProject İş durumu" - description: "Arka plan işlerinin durumu ve listelenmesi." + description: "Arka plan işlerinin durumu listeleniyor." job_status_dialog: download_starts: 'İndirme işlemi otomatik olarak başlayacaktır.' link_to_download: 'Veya indirmek için %{link}.' diff --git a/modules/meeting/config/locales/crowdin/es.yml b/modules/meeting/config/locales/crowdin/es.yml index 46fcaed4f0c..fe59be3167e 100644 --- a/modules/meeting/config/locales/crowdin/es.yml +++ b/modules/meeting/config/locales/crowdin/es.yml @@ -231,23 +231,23 @@ es: header: "Cancelada: Reunión «%{title}»" header_occurrence: "Cancelada: Repetición de la reunión «%{title}»" header_series: "Cancelada: Serie de reuniones «%{title}»" - summary_occurrence: "An occurrence of '%{title}' has been cancelled by %{actor}, or you have been removed as a participant" - summary_series: "Meeting series '%{title}' has been cancelled by %{actor}, or you have been removed as a participant" - summary: "'%{title}' has been cancelled by %{actor}, or you have been removed as a participant" + summary_occurrence: "%{actor} ha cancelado una repetición de «%{title}» o se le ha eliminado como participante" + summary_series: "%{actor} ha cancelado la serie de reuniones «%{title}» o se le ha eliminado como participante" + summary: "%{actor} ha cancelado «%{title}» o se le ha eliminado como participante" date_time: "Fecha/hora programada" participant_added: - header: "Meeting '%{title}' - Participant added" - header_series: "Meeting series '%{title}' - Participant added" - summary: "%{actor} added %{participant} to the meeting '%{title}'" - summary_series: "%{actor} added %{participant} to the meeting series '%{title}'" + header: "Reunión «%{title}»: se añadió un participante" + header_series: "Serie de reuniones «%{title}»: se añadió un participante" + summary: "%{actor} ha añadido a %{participant} a la reunión «%{title}»" + summary_series: "%{actor} ha añadido a %{participant} a la serie de reuniones «%{title}»" participant_removed: - header: "Meeting '%{title}' - Participant removed" - header_series: "Meeting series '%{title}' - Participant removed" - summary: "%{actor} removed %{participant} from the meeting '%{title}'" - summary_series: "%{actor} removed %{participant} from the meeting series '%{title}'" + header: "Reunión «%{title}»: se eliminó un participante" + header_series: "Serie de reuniones «%{title}»: se eliminó un participante" + summary: "%{actor} ha eliminado a %{participant} de la reunión «%{title}»" + summary_series: "%{actor} ha eliminado a %{participant} de la serie de reuniones «%{title}»" ended: header_series: "Terminada: serie de reuniones «%{title}»" - summary_series: "Meeting series '%{title}' has been ended by %{actor}" + summary_series: "%{actor} ha terminado la serie de reuniones «%{title}»" updated: header: "La reunión «%{title}» se ha actualizado" summary: "%{actor} ha actualizado la reunión «%{title}»" @@ -530,8 +530,8 @@ es: label_agenda_item_move_up: "Mover hacia arriba" label_agenda_item_move_down: "Mover hacia abajo" label_agenda_item_duplicate: "Duplicar" - label_agenda_item_duplicate_in_next: "Duplicate in next meeting" - label_agenda_item_duplicate_in_next_title: "Duplicate in next meeting?" + label_agenda_item_duplicate_in_next: "Duplicar en la próxima reunión" + label_agenda_item_duplicate_in_next_title: "¿Duplicar en la próxima reunión?" label_agenda_item_add_notes: "Añadir notas" label_agenda_item_add_outcome: "Añadir resultado" label_agenda_item_work_package_add: "Añadir paquete de trabajo" @@ -542,9 +542,9 @@ es: label_agenda_outcome_actions: "Acciones resultantes de la agenda" label_agenda_outcome_edit: "Editar resultado" label_agenda_outcome_delete: "Eliminar resultado" - label_added_as_outcome: "Added as outcome" - label_write_outcome: "Write outcome" - label_existing_work_package: "Existing work package" + label_added_as_outcome: "Añadido como resultado" + label_write_outcome: "Escribir resultado" + label_existing_work_package: "Paquete de trabajo existente" text_outcome_not_editable_anymore: "Este resultado ya no se puede editar." text_outcome_cannot_be_added: "Ya no se puede añadir ningún resultado." label_backlog_clear: "Borrar backlog" @@ -611,9 +611,9 @@ es: text_agenda_item_duplicate_in_next_meeting: "¿Seguro que deseas añadir una copia de este punto del orden del día a la próxima reunión, el %{date} a las %{time}? Los resultados no se duplicarán." text_agenda_item_duplicated_in_next_meeting: "Punto del orden del día duplicado a la próxima reunión, el %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "Este paquete de trabajo no está todavía programado en ninguna agenda de próximas reuniones." - text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." - text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." - text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." + text_agenda_item_no_available_occurrence: "Se han cancelado todas las próximas repeticiones." + text_agenda_item_dialog_skipping_cancelled_one: "Nota: Se omite la repetición cancelada el %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Nota: Se omiten %{count} repeticiones canceladas." text_work_package_add_to_meeting_hint: 'Utilice el botón "Añadir a la reunión" para añadir este paquete de trabajo a una próxima reunión.' text_work_package_has_no_past_meeting_agenda_items: "Este paquete de trabajo no se añadió como punto del orden del día en una reunión anterior." text_email_updates_muted: "Las actualizaciones de calendario por correo electrónico están silenciadas. Los participantes no recibirán invitaciones actualizadas por correo electrónico cuando realice cambios." @@ -621,12 +621,12 @@ es: my_account: access_tokens: token/ical_meeting: - blank_description: "You can create one using the button below." - blank_title: "No iCalendar meeting token" + blank_description: "Puede crear uno utilizando el botón de abajo." + blank_title: "No hay token de reunión de iCalendar" title: "iCalendar para reuniones" - table_title: "iCalendar meeting tokens" - text_hint: "Los tokens de reuniones iCalendar permiten a los usuarios suscribirse a todas sus reuniones y ver información actualizada de las reuniones en clientes externos." - disabled_text: "Las suscripciones a las reuniones iCalendar no están habilitadas por el administrador. Póngase en contacto con su administrador para utilizar esta función." + table_title: "Tokens de reunión de iCalendar" + text_hint: "Los tokens de reunión de iCalendar permiten a los usuarios suscribirse a todas sus reuniones y ver información actualizada de las reuniones en clientes externos." + disabled_text: "Las suscripciones a las reuniones de iCalendar no están habilitadas por el administrador. Póngase en contacto con su administrador para utilizar esta función." add_button: "Suscribirse al calendario" my: access_token: diff --git a/modules/meeting/config/locales/crowdin/ja.yml b/modules/meeting/config/locales/crowdin/ja.yml index a9ac653488e..2714f9617eb 100644 --- a/modules/meeting/config/locales/crowdin/ja.yml +++ b/modules/meeting/config/locales/crowdin/ja.yml @@ -80,7 +80,7 @@ ja: meeting_agenda: "アジェンダ" meeting_section: "セクション" token/ical_meeting: - other: "iCalミーティング予約" + other: "iCal会議予約" activity: filter: meeting: "会議" @@ -113,7 +113,7 @@ ja: label_meeting_new_dynamic: "新しい一度限りの会議" label_meeting_new_recurring: "定期的な会議の作成" label_meeting_create: "会議を作成" - label_meeting_duplicate: "ミーティングを複製" + label_meeting_duplicate: "会議を複製" label_meeting_edit: "会議を編集" label_meeting_agenda: "アジェンダ" label_meeting_minutes: "議事録" @@ -126,7 +126,7 @@ ja: label_meeting_date_and_time: "日付と時刻" label_meeting_diff: "差分" label_meeting_send_updates: "カレンダーの招待と更新をメールで送信" - label_meeting_send_updates_caption: "このミーティングのすべての参加者に電子メールの招待を送信" + label_meeting_send_updates_caption: "この会議のすべての参加者に電子メールの招待を送信" label_recurring_meeting: "定期的な会議" label_recurring_meeting_part_of: "一連の会議の一部" label_recurring_meeting_new: "新しい繰り返しの会議" @@ -134,13 +134,13 @@ ja: label_template: "テンプレート" label_recurring_meeting_view: "一連の会議を表示する" label_recurring_meeting_create: "開く" - label_recurring_meeting_duplicate: "一回限りのミーティングとして複製" + label_recurring_meeting_duplicate: "一回限りの会議として複製" label_recurring_meeting_cancel: "この予定をキャンセル" label_recurring_meeting_delete: "予定を削除" label_recurring_meeting_restore: "この予定を復元" label_recurring_meeting_schedule: "スケジュール" label_recurring_meeting_next_occurrence: "次の会議" - label_recurring_meeting_no_end_date: "他にも予定されているミーティングがあります(%{schedule})。" + label_recurring_meeting_no_end_date: "他にも予定されている会議があります(%{schedule})。" label_recurring_meeting_more: other: "There are %{count} more scheduled meetings (%{schedule})." label_recurring_meeting_more_past: @@ -211,25 +211,25 @@ ja: 上記で選択した参加者にすぐにメール招待を送信します。また、後で手動で行うこともできます。 send_invitation_emails_structured: "すべての参加者にすぐにメール招待を送信します。また、後で手動で行うこともできます。" open_meeting_link: "会議を開く" - open_my_meetings_link: "自分のミーティングに移動" + open_my_meetings_link: "自分の会議に移動" series: - title: "[%{project_name}] ミーティングシリーズ '%{title}'" + title: "[%{project_name}] 一連の会議 '%{title}'" summary: "%{actor} has invited you to a new meeting series '%{title}'" series_updated: title: "[%{project_name}]の一連の会議 「%{title}」が更新されました" - summary: "ミーティングシリーズ '%{title}' は %{actor}によって更新されました" + summary: "一連の会議 '%{title}' は %{actor}によって更新されました" old_schedule: "古いスケジュール" new_schedule: "新しいスケジュール" invited: - summary: "%{actor} がミーティング「%{title}」への招待を送信しました" + summary: "%{actor} が会議「%{title}」への招待を送信しました" cancelled: header: "キャンセルされました:会議「%{title}」" - header_occurrence: "キャンセル: ミーティング発生'%{title}'" - header_series: "キャンセル: ミーティングシリーズ '%{title}'" - summary_occurrence: "%{title}'の発生は %{actor}によってキャンセルされました。" - summary_series: "ミーティングシリーズ '%{title}' は、 %{actor} によってキャンセルされました。" - summary: "'%{title}' は %{actor} によってキャンセルされました。" - date_time: "予定日時" + header_occurrence: "キャンセル: 会議発生'%{title}'" + header_series: "キャンセル: 一連の会議 '%{title}'" + summary_occurrence: "An occurrence of '%{title}' has been cancelled by %{actor}, or you have been removed as a participant" + summary_series: "Meeting series '%{title}' has been cancelled by %{actor}, or you have been removed as a participant" + summary: "'%{title}' has been cancelled by %{actor}, or you have been removed as a participant" + date_time: "スケジュールされた日時" participant_added: header: "Meeting '%{title}' - Participant added" header_series: "Meeting series '%{title}' - Participant added" @@ -244,9 +244,9 @@ ja: header_series: "Ended: Meeting series '%{title}'" summary_series: "Meeting series '%{title}' has been ended by %{actor}" updated: - header: "ミーティング '%{title}' が更新されました" - summary: "ミーティング '%{title}' は %{actor} によって更新されました" - body: "ミーティング '%{title}' は、 %{actor} によって更新されました。" + header: "会議 '%{title}' が更新されました" + summary: "会議 '%{title}' は %{actor} によって更新されました" + body: "会議 '%{title}' は、 %{actor} によって更新されました。" old_title: "Old title" new_title: "New title" old_date_time: "古い日付/時刻" @@ -260,7 +260,7 @@ ja: recurring_text: "それぞれの予定に対する動的なテンプレートを持つ一連の会議を作成します。" structured_text: "会議を議題項目のリストとして整理し、オプションでそれらをワークパッケージにリンクさせることができます。" structured_text_copy: "会議をコピーする場合、現在は関連する議題項目はコピーされず、詳細のみがコピーされます。" - copied: "ミーティング#%{id} からコピーしました" + copied: "会議#%{id} からコピーしました" delete_dialog: one_time: title: "会議を削除" @@ -268,17 +268,17 @@ ja: confirmation_message_html: > この操作は元に戻せません。続行しますか? occurrence: - title: "ミーティングのキャンセル" - heading: "このミーティングをキャンセルしますか?" + title: "会議のキャンセル" + heading: "この会議をキャンセルしますか?" confirmation_message_html: > テンプレートにない会議情報は失われます。 続けますか? confirm_button: "発生をキャンセル" blankslate: - title: "表示するミーティングがありません" - desc: "新しいミーティングを作成したりフィルタ条件を変更することができます" + title: "表示する会議がありません" + desc: "新しい会議を作成したりフィルタ条件を変更することができます" label_export_pdf: "PDF形式でエクスポート" export: - your_meeting_export: "ミーティングはエクスポートされています" + your_meeting_export: "会議はエクスポートされています" minutes: footer_page_numbers: "P. %{current_page} / %{total_pages}" author: "作成者" @@ -292,10 +292,10 @@ ja: templates: default: label: デフォルト - caption: デフォルトのテンプレートは、ほとんどのミーティングに適しており、現在の状態を表します。 + caption: デフォルトのテンプレートは、ほとんどの会議に適しており、現在の状態を表します。 minutes: label: 分 - caption: 議事録テンプレートは、閉じられ、アーカイブされたミーティングに適しています。 + caption: 議事録テンプレートは、閉じられ、アーカイブされた会議に適しています。 first_page_header_left: label: 最初のページヘッダを左 caption: このテキストはヘッダーの左側の最初のページに表示されます。 @@ -333,7 +333,7 @@ ja: enable: > すべての参加者は、会議の日時、場所、参加者に変更があるたびに、更新されたカレンダーの招待メールを受け取ります。有効にすると、参加者全員にすぐにメールが送信されます。 disable: > - ミーティングの日付、時間、場所または参加者に変更があった場合、参加者はメールで更新されたカレンダー招待を受信できなくなります。 彼らがすでにこの会議のための招待を持っていた場合、それはもはや正確ではないかもしれません。 + 会議の日付、時間、場所または参加者に変更があった場合、参加者はメールで更新されたカレンダー招待を受信できなくなります。 彼らがすでにこの会議のための招待を持っていた場合、それはもはや正確ではないかもしれません。 confirm_label: enable: "メールアドレスの更新を有効にする" disable: "メールアドレスの更新を無効にする" @@ -347,17 +347,17 @@ ja: enabled: > 参加者を追加または削除すると、すべての参加者にメールで更新されたカレンダー招待状が届きます。 disabled: > - 参加者は、ミーティングの日付、時間、参加者への変更を知らせるメールを受信しません。 + 参加者は、会議の日付、時間、参加者への変更を知らせるメールを受信しません。 occurrence: enabled: > - ミーティングシリーズのメールカレンダーの更新が有効になっています.すべての参加者は,この発生に対するあなたの変更を通知する更新されたカレンダー招待を受け取ります。 + 一連の会議のメールカレンダーの更新が有効になっています.すべての参加者は,この発生に対するあなたの変更を通知する更新されたカレンダー招待を受け取ります。 disabled: > - ミーティングシリーズのメールカレンダーの更新は無効になっています。出席者はこの出来事に対するあなたの変更を通知するメールを受信しません。 + 一連の会議のメールカレンダーの更新は無効になっています。出席者はこの出来事に対するあなたの変更を通知するメールを受信しません。 template: enabled: > - ミーティングシリーズでは,メールカレンダーの更新が有効になります. すべての参加者は、更新されたカレンダー招待状を受け取り、このテンプレートまたは個々のオカレンダーへの変更を通知します。 + 一連の会議では,メールカレンダーの更新が有効になります. すべての参加者は、更新されたカレンダー招待状を受け取り、このテンプレートまたは個々のオカレンダーへの変更を通知します。 disabled: > - ミーティングシリーズのメールカレンダーの更新は無効になっています. 参加者は、このテンプレートまたは個々のオカレンスへの変更を通知するメールを受信しません。 + 一連の会議のメールカレンダーの更新は無効になっています. 参加者は、このテンプレートまたは個々のオカレンスへの変更を通知するメールを受信しません。 presentation_mode: title: "プレゼンテーションモード" button_present: "プレゼント" @@ -370,7 +370,7 @@ ja: no_items_flash: "発表する議題はない。" ical_response: update_failed: "参加ステータスを更新できませんでした。" - meeting_not_found: "指定されたUIDのミーティングが見つからない。" + meeting_not_found: "指定されたUIDの会議が見つからない。" meeting_section: untitled_title: "タイトルなしセクション" delete_confirmation: "セクションを削除すると、そのすべての議題項目も削除されます。これを実行してもよろしいですか?" @@ -387,17 +387,17 @@ ja: time_zone_difference_banner: title: "タイムゾーンの差" description: > - 以下の日付は、ミーティング・シリーズのタイムゾーン(%{actual_zone})を参照しており、お住まいの地域のタイムゾーン(%{user_zone})ではありません。 + 以下の日付は、一連の会議のタイムゾーン(%{actual_zone})を参照しており、お住まいの地域のタイムゾーン(%{user_zone})ではありません。 ended_blankslate: title: "一連の会議が終了しました" message: "一連の会議は終了しました。今後の会議はありません " action: "過去の会議を閲覧したり、会議シリーズを編集して拡張することもできます。" occurrence: - infoline: "このミーティングは、定期的に開催されるミーティング・シリーズの一環である。" + infoline: "この会議は、定期的に開催される一連の会議の一環である。" error_no_next: "次回の開催はありません。" - first_already_exists: "このミーティングシリーズの最初の発生は既にサイト化されています。" + first_already_exists: "この一連の会議の最初の発生は既にサイト化されています。" first_created: > - 最初の会議がテンプレートから正常に作成されました。今後作成されるミーティングはすべて、前回の開催時に自動的に作成されます。 + 最初の会議がテンプレートから正常に作成されました。今後作成される会議はすべて、前回の開催時に自動的に作成されます。 template: button_finalize: "最初の会議を開く" blank_title: "一連の会議のテンプレートがありません" @@ -406,9 +406,9 @@ ja: label_view_template: "テンプレートを表示" label_edit_template: "テンプレートを編集" banner_html: > - 現在ミーティングシリーズのテンプレートを編集しています: %{link}. シリーズ内で新しいミーティングが発生するたびにこのテンプレートが使用されます.変更は過去または既に作成されたミーティングには影響しません.。 + 現在一連の会議のテンプレートを編集しています: %{link}. シリーズ内で新しい会議が発生するたびにこのテンプレートが使用されます.変更は過去または既に作成された会議には影響しません.。 draft_banner_html: > - 現在ミーティングシリーズのテンプレートを編集しています: %{link}. シリーズのミーティングの新しい発生ごとに、このテンプレートが使用されます。 変更は過去またはすでに作成されたミーティングには影響しません.最初のミーティングを開くまでは,この下書きモードでは電子メールの招待は送信されません。 + 現在一連の会議のテンプレートを編集しています: %{link}. シリーズの会議の新しい発生ごとに、このテンプレートが使用されます。 変更は過去またはすでに作成された会議には影響しません.最初の会議を開くまでは,この下書きモードでは電子メールの招待は送信されません。 frequency: x_daily: other: "%{count} 日ごと" @@ -433,31 +433,31 @@ ja: open: title: "議題が開かれました" subtitle: > - 開いているミーティングには、編集可能な議題があり、個々のユーザーの「私のミーティング」セクションに表示されます。 ミーティングシリーズテンプレートの変更は、すでに開いているミーティングの発生には影響しません。 + 開いている会議には、編集可能な議題があり、個々のユーザーの「私の会議」セクションに表示されます。 一連の会議テンプレートの変更は、すでに開いている会議の発生には影響しません。 blankslate: - title: "現在公開されているミーティングはありません" - desc: "下の「開く」ボタンをクリックして手動でミーティングを開くことができます" + title: "現在公開されている会議はありません" + desc: "下の「開く」ボタンをクリックして手動で会議を開くことができます" planned: title: "計画" subtitle: > - 定期的なミーティングスケジュールには以下のミーティングが予定されていますが、まだ開いていません。 予定されている会議が始まるたびに、次の会議が自動的に開かれます。 計画されたミーティングを手動で開いてテンプレートをインポートして議題の編集を開始することもできます。 + 定期的な会議スケジュールには以下の会議が予定されていますが、まだ開いていません。 予定されている会議が始まるたびに、次の会議が自動的に開かれます。 計画された会議を手動で開いてテンプレートをインポートして議題の編集を開始することもできます。 blankslate: title: "予定されている会議はありません" desc: > 一連の会議に追加の会議は予定されていません。追加の会議をスケジュールしたり、一連の会議に追加する場合には、テンプレートに移動して会議の詳細から終了日、頻度、間隔を変更します。 delete_dialog: title: "一連の会議を削除" - heading: "このミーティングシリーズを完全に削除しますか?" + heading: "この一連の会議を完全に削除しますか?" confirmation_message_html: zero: > - ミーティングシリーズ %{title}には会議の発生がありません。このシリーズは削除されます。ご注意ください。 + 一連の会議 %{title}には会議の発生がありません。このシリーズは削除されます。ご注意ください。 one: > %{title} を削除すると、このシリーズで発生したものも削除されます。この操作は元に戻せません。注意してください。 other: > %{title} を削除すると、この系列内のすべての %{count} オカレンスが削除されます。この操作は元に戻せません。注意してください。 scheduled_delete_dialog: - title: "ミーティングのキャンセル" - heading: "このミーティングをキャンセルしますか?" + title: "会議のキャンセル" + heading: "この会議をキャンセルしますか?" confirmation_message_html: > テンプレートにない会議情報は失われます。 続行しますか? confirm_button: "この予定をキャンセル" @@ -473,26 +473,26 @@ ja: permission_manage_agendas: "議題を管理する" permission_manage_agendas_explanation: "議題項目の作成、編集、削除を許可" permission_manage_outcomes: "アウトカムを管理" - permission_send_meeting_invites_and_outcomes: "ミーティングの招待状と結果を参加者に送信する" + permission_send_meeting_invites_and_outcomes: "会議の招待状と結果を参加者に送信する" project_module_meetings: "会議" text_duration_in_hours: "期間(時間)" text_in_hours: "数時間以内" text_meeting_agenda_for_meeting: '会議の議題 "%{meeting}"' text_meeting_series_end_early_heading: "今後の予定を削除しますか?" - text_meeting_series_end_early: "シリーズを終了すると、今後開かれたミーティングや予定されたミーティングの発生が削除されます" + text_meeting_series_end_early: "シリーズを終了すると、今後開かれた会議や予定された会議の発生が削除されます" text_meeting_closing_are_you_sure: "会議アジェンダを終了してもよろしいですか?" text_meeting_agenda_open_are_you_sure: "数分内のすべての変更が上書きされます!続けますか?" text_meeting_minutes_for_meeting: '会議の議事録 "%{meeting}"' - text_notificiation_invited: "このメールには以下のミーティングのicsエントリーが含まれています:" + text_notificiation_invited: "このメールには以下の会議のicsエントリーが含まれています:" text_meeting_ics_description: >- - ミーティングへのリンク: %{url} + 会議へのリンク: %{url} text_meeting_ics_meeting_series_description: >- - ミーティングシリーズへのリンク: %{url} + 一連の会議へのリンク: %{url} text_meeting_occurrence_ics_description: >- - 会議発生へのリンク %{url} ミーティングシリーズへのリンク: %{series_url} + 会議発生へのリンク %{url} 一連の会議へのリンク: %{series_url} text_meeting_empty_heading: "あなたの会議は空です。" text_meeting_empty_description1: "まず、以下に議題項目を追加してください。各項目はタイトルだけの簡単なものでも構いませんが、所要時間やメモなどの詳細を追加することもできます。" - text_meeting_empty_description2: '既存のワークパッケージへの参照を追加することもできます。これを行うと、関連するメモがその作業パッケージの「会議」タブに自動的に表示されます。' + text_meeting_empty_description2: '既存のワークパッケージへの参照を追加することもできます。これを行うと、関連するメモがそのワークパッケージの「会議」タブに自動的に表示されます。' label_meeting_empty_action: "議題項目を追加" label_meeting_actions: "会議の対応事項" label_meeting_edit_title: "会議タイトルを編集" @@ -545,22 +545,22 @@ ja: text_backlog_clear_error: "バックログのクリア中にエラーが発生しました。" label_agenda_backlog: "アジェンダ・バックログ" text_agenda_backlog: > - このバックログは、このワンタイムミーティングに固有のものです.アイテムをドラッグして追加またはミーティングの議題から削除することができます. + このバックログは、このワンタイム会議に固有のものです.アイテムをドラッグして追加または会議の議題から削除することができます. label_agenda_backlog_clear_title: "議題のバックログをクリアしますか?" text_agenda_backlog_clear_description: > 議題のバックログ内のすべての項目を削除してもよろしいですか?この操作は取り消せません。 label_series_backlog: "シリーズバックログ" text_series_backlog: > - バックログはこのシリーズのすべての出現と共有されます。 項目をドラッグして、特定のミーティングから項目を追加または削除できます。 + バックログはこのシリーズのすべての出現と共有されます。 項目をドラッグして、特定の会議から項目を追加または削除できます。 label_series_backlog_clear_title: "シリーズバックログをクリアしますか?" text_series_backlog_clear_description: > - これにより、シリーズのすべてのミーティングと共有されるシリーズバックログ内のすべての項目が削除されます。 続行してもよろしいですか?この操作は元に戻せません。 + これにより、シリーズのすべての会議と共有されるシリーズバックログ内のすべての項目が削除されます。 続行してもよろしいですか?この操作は元に戻せません。 text_agenda_item_title: '議題項目 "%{title}"' text_agenda_work_package_deleted: "削除されたワークパッケージの議題項目" text_deleted_agenda_item: "削除された議題項目" label_initial_meeting_details: "会議" label_meeting_details: "会議詳細" - label_meeting_series_details: "ミーティングシリーズの詳細" + label_meeting_series_details: "一連の会議の詳細" label_meeting_details_edit: "会議詳細を編集" label_meeting_state: "会議のステータス" label_meeting_state_draft: "ドラフト" @@ -575,15 +575,15 @@ ja: label_meeting_close_action: "会議を終了する" label_meeting_in_progress_action: "会議を開始" label_meeting_open_action: "会議を開く" - text_meeting_draft_description: "ドラフトモードで議題を準備します。 ミーティングの詳細を変更したり出席者を追加/削除したりしても,このミーティングはカレンダーの更新や招待状を送信しません。" + text_meeting_draft_description: "ドラフトモードで議題を準備します。 会議の詳細を変更したり出席者を追加/削除したりしても,この会議はカレンダーの更新や招待状を送信しません。" text_meeting_open_description: "議題項目と参加者を追加/削除することができます。議題が準備できたら、成果を文書化するために進行中であることを示します。" text_meeting_closed_description: "この会議は終了しています。これ以上、議題項目の追加/削除はできません。" - text_meeting_in_progress_description: "議題を変更したり、各項目のアウトカムを記録したり、参加者の出席を追跡することができます。 ミーティングが完了すると、ミーティングをクローズとしてマークしてロックできます。" + text_meeting_in_progress_description: "議題を変更したり、各項目のアウトカムを記録したり、参加者の出席を追跡することができます。 会議が完了すると、会議をクローズとしてマークしてロックできます。" text_meeting_open_dropdown_description: "既存の結果は残りますが、ユーザーは新しい結果を追加することはできません。" text_meeting_in_progress_dropdown_description: "会議中に取られた情報のニーズや意思決定などの成果を記録します。" text_meeting_closed_dropdown_description: "この会議は終了しました。これ以上、議題や結果を変更することはできません。" - text_meeting_draft_banner: "現在下書きモードです。 ミーティングの詳細を変更したり出席者を追加/削除したりしても,このミーティングはカレンダーの更新や招待状を送信しません。" - text_exit_draft_mode_dialog_title: "このミーティングを開いて招待を送信しますか?" + text_meeting_draft_banner: "現在下書きモードです。 会議の詳細を変更したり出席者を追加/削除したりしても,この会議はカレンダーの更新や招待状を送信しません。" + text_exit_draft_mode_dialog_title: "この会議を開いて招待を送信しますか?" text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." @@ -595,12 +595,12 @@ ja: label_meeting_selection_caption: "このワークパッケージは、今後の会議または進行中の会議にのみ追加可能です。" label_section_selection_caption: "Choose a particular section of the agenda or add it to the backlog." placeholder_section_select_meeting_first: "Meeting selection is required first" - text_add_work_package_to_meeting_form: "選択したミーティングまたは議題項目としてバックログにワークパッケージが追加されます。" + text_add_work_package_to_meeting_form: "選択した会議または議題項目としてバックログにワークパッケージが追加されます。" text_add_work_package_to_meeting_description: "ワークパッケージは、議論のために1つまたは複数の会議に追加できます。それに関するメモもここで表示されます。" text_agenda_item_no_notes: "メモは提供されていません" text_agenda_item_not_editable_anymore: "この議題項目はもう編集できません。" - text_agenda_item_move_next_meeting: "このアイテムは、 %{date} の %{time} の次のミーティングに移動されます。" - text_agenda_item_moved_to_next_meeting: "議題アイテムが %{date}の次のミーティングに移動されました" + text_agenda_item_move_next_meeting: "このアイテムは、 %{date} の %{time} の次の会議に移動されます。" + text_agenda_item_moved_to_next_meeting: "議題アイテムが %{date}の次の会議に移動されました" text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "このワークパッケージは、まだ今後の会議の議題に予定されていません。" @@ -618,7 +618,7 @@ ja: blank_title: "No iCalendar meeting token" title: "iCalendar for meetings" table_title: "iCalendar meeting tokens" - text_hint: "iCalendarミーティングトークンを使用すると、ユーザーはすべてのミーティングを購読し、外部クライアントで最新のミーティング情報を表示できます。" + text_hint: "iCalendar会議トークンを使用すると、ユーザーはすべての会議を購読し、外部クライアントで最新の会議情報を表示できます。" disabled_text: "iCalendarの会議予約は管理者によって有効化されていません。この機能を使用するには、管理者に連絡してください。" add_button: "カレンダーを購読する" my: @@ -626,15 +626,15 @@ ja: dialog: token/ical_meeting: dialog_title: "会議用の新しいiCalサブスクリプショントークン" - dialog_body: "このトークンは、外部カレンダーアプリケーションですべてのミーティングを表示することができるiCalサブスクリプションURLを生成します。" + dialog_body: "このトークンは、外部カレンダーアプリケーションですべての会議を表示することができるiCalサブスクリプションURLを生成します。" create_button: "サブスクリプションを作成" name_label: "トークン名" name_caption: '「マイフォン」や「ワークコンピュータ」など、どこで使用するかにちなんで名付けることができます。' created_dialog: token/ical_meeting: - title: "iCalミーティング購読トークンが生成されました" - body: "以下のURLをパスワードと同じように扱ってください。アクセス権を持っている人はすべてのミーティングを閲覧できます。" + title: "iCal会議購読トークンが生成されました" + body: "以下のURLをパスワードと同じように扱ってください。アクセス権を持っている人はすべての会議を閲覧できます。" revocation: token/ical_meeting: - notice_success: "iCalendar ミーティング購読が正常に取り消されました。" - notice_failure: "iCalendar ミーティング購読の取り消しに失敗しました: %{error}" + notice_success: "iCalendar 会議購読が正常に取り消されました。" + notice_failure: "iCalendar 会議購読の取り消しに失敗しました: %{error}" diff --git a/modules/meeting/config/locales/crowdin/pt-BR.yml b/modules/meeting/config/locales/crowdin/pt-BR.yml index ee9274b9415..1b7b5b046e2 100644 --- a/modules/meeting/config/locales/crowdin/pt-BR.yml +++ b/modules/meeting/config/locales/crowdin/pt-BR.yml @@ -231,9 +231,9 @@ pt-BR: header: "Cancelada: Reunião \"%{title}\"" header_occurrence: "Cancelada: Ocorrência de reunião \"%{title}\"" header_series: "Cancelada: série de reuniões \"%{title}\"" - summary_occurrence: "An occurrence of '%{title}' has been cancelled by %{actor}, or you have been removed as a participant" - summary_series: "Meeting series '%{title}' has been cancelled by %{actor}, or you have been removed as a participant" - summary: "'%{title}' has been cancelled by %{actor}, or you have been removed as a participant" + summary_occurrence: "Uma ocorrência de \"%{title}\" foi cancelada por %{actor}, ou você foi removido como participante" + summary_series: "A série de reuniões \"%{title}\" foi cancelada por %{actor}, ou você foi removido como participante" + summary: "\"%{title}\" foi cancelada por %{actor}, ou você foi removido como participante" date_time: "Data/hora programada" participant_added: header: "Reunião \"%{title}\" - Participante adicionado" @@ -247,7 +247,7 @@ pt-BR: summary_series: "%{actor} removeu %{participant} da série de reuniões \"%{title}\"" ended: header_series: "Encerrada: série de reuniões \"%{title}\"" - summary_series: "Meeting series '%{title}' has been ended by %{actor}" + summary_series: "A série de reuniões \"%{title}\" foi encerrada por %{actor}" updated: header: "A reunião \"%{title}\" foi atualizada" summary: "A reuniões \"%{title}\" foi atualizada por %{actor}" @@ -530,8 +530,8 @@ pt-BR: label_agenda_item_move_up: "Mover para cima" label_agenda_item_move_down: "Mover para baixo" label_agenda_item_duplicate: "Duplicar" - label_agenda_item_duplicate_in_next: "Duplicate in next meeting" - label_agenda_item_duplicate_in_next_title: "Duplicate in next meeting?" + label_agenda_item_duplicate_in_next: "Duplicar na próxima reunião" + label_agenda_item_duplicate_in_next_title: "Duplicar na próxima reunião?" label_agenda_item_add_notes: "Adicionar anotações" label_agenda_item_add_outcome: "Adicionar resultado" label_agenda_item_work_package_add: "Adicionar pacote de trabalho" @@ -543,8 +543,8 @@ pt-BR: label_agenda_outcome_edit: "Editar resultado" label_agenda_outcome_delete: "Remover resultado" label_added_as_outcome: "Adicionado como resultado" - label_write_outcome: "Write outcome" - label_existing_work_package: "Existing work package" + label_write_outcome: "Gravar resultado" + label_existing_work_package: "Pacote de trabalho existente" text_outcome_not_editable_anymore: "Este resultado não pode mais ser editado." text_outcome_cannot_be_added: "Não é mais possível adicionar um resultado." label_backlog_clear: "Limpar backlog" @@ -611,9 +611,9 @@ pt-BR: text_agenda_item_duplicate_in_next_meeting: "Tem certeza de que deseja adicionar uma cópia deste item da agenda à próxima reunião, em %{date} às %{time}? Os resultados não serão duplicados." text_agenda_item_duplicated_in_next_meeting: "Item da agenda duplicado na próxima reunião, em %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "Este pacote de trabalho ainda não está programado na agenda de uma reunião futura." - text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." - text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." - text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." + text_agenda_item_no_available_occurrence: "Todas as ocorrências futuras foram canceladas." + text_agenda_item_dialog_skipping_cancelled_one: "Observação: Ignorar a ocorrência cancelada em %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Observação: Ignorar %{count} ocorrências canceladas." text_work_package_add_to_meeting_hint: 'Use o botão "Adicionar à reunião" para adicionar este pacote de trabalho a uma próxima reunião.' text_work_package_has_no_past_meeting_agenda_items: "Este pacote de trabalho não foi adicionado como item da agenda em uma reunião anterior." text_email_updates_muted: "As atualizações de calendário por e-mail estão silenciadas. Os participantes não receberão convites atualizados por e-mail quando você fizer alterações." @@ -621,10 +621,10 @@ pt-BR: my_account: access_tokens: token/ical_meeting: - blank_description: "You can create one using the button below." - blank_title: "No iCalendar meeting token" + blank_description: "Você pode criar uma usando o botão abaixo." + blank_title: "Sem token de reunião do iCalendar" title: "iCalendar para reuniões" - table_title: "iCalendar meeting tokens" + table_title: "Tokens de reunião do iCalendar" text_hint: "Os tokens de reunião por iCalendar permitem que os usuários assinem todas as suas reuniões e visualizem informações atualizadas das reuniões em clientes externos." disabled_text: "As assinaturas de reuniões iCalendar não estão habilitadas pelo administrador. Entre em contato com o administrador para usar este recurso." add_button: "Assinar calendário" diff --git a/modules/meeting/config/locales/crowdin/tr.yml b/modules/meeting/config/locales/crowdin/tr.yml index 40781267635..aea81ff1d70 100644 --- a/modules/meeting/config/locales/crowdin/tr.yml +++ b/modules/meeting/config/locales/crowdin/tr.yml @@ -24,7 +24,7 @@ tr: plugin_openproject_meeting: name: "OpenProject Toplantı" description: >- - Bu modül OpenProject'e proje toplantılarını destekleyen fonksiyonlar ekler. Toplantılar, aynı projeden toplantıya katılacak davetliler seçilerek planlanabilir. Bir gündem oluşturulabilir ve davetlilere gönderilebilir. Toplantıdan sonra katılımcılar seçilebilir ve gündeme göre tutanaklar oluşturulabilir. Son olarak, tutanaklar tüm katılımcılara ve davetlilere gönderilebilir. + Bu modül, OpenProject’te proje toplantılarının yönetilmesini sağlar. Aynı projedeki kullanıcılar toplantıya davet edilerek planlama yapılabilir, gündem hazırlanıp paylaşılabilir, toplantı sonrası tutanak oluşturulabilir ve tüm katılımcılara gönderilebilir. activerecord: attributes: meeting: diff --git a/modules/meeting/config/locales/crowdin/vi.yml b/modules/meeting/config/locales/crowdin/vi.yml index fbca599b752..0ba332b18df 100644 --- a/modules/meeting/config/locales/crowdin/vi.yml +++ b/modules/meeting/config/locales/crowdin/vi.yml @@ -226,9 +226,9 @@ vi: header: "Đã hủy: Cuộc họp '%{title}'" header_occurrence: "Đã hủy: Cuộc họp diễn ra '%{title}'" header_series: "Đã hủy: Chuỗi cuộc họp '%{title}'" - summary_occurrence: "Sự xuất hiện của '%{title}' đã bị hủy bởi %{actor}." - summary_series: "Chuỗi cuộc họp '%{title}' đã bị hủy bởi %{actor}." - summary: "'%{title}' đã bị hủy bởi %{actor}." + summary_occurrence: "Sự kiện '%{title}' đã bị hủy bởi %{actor}, hoặc bạn đã bị loại khỏi danh sách tham gia" + summary_series: "Chuỗi cuộc họp '%{title}' đã bị hủy bởi %{actor}, hoặc bạn đã bị loại khỏi danh sách tham gia" + summary: "'%{title}' đã bị hủy bởi ' %{actor}' hoặc bạn đã bị loại khỏi danh sách người tham gia" date_time: "Ngày/giờ dự kiến" participant_added: header: "Họp '%{title}' - Thành viên tham gia đã được thêm vào" @@ -242,7 +242,7 @@ vi: summary_series: "%{actor} Đã loại bỏ \" %{participant} \" khỏi chuỗi cuộc họp \"%{title}\"" ended: header_series: "Đã kết thúc: Chuỗi cuộc họp '%{title}'" - summary_series: "Chuỗi cuộc họp '%{title}' đã kết thúc bởi %{actor}." + summary_series: "Chuỗi hội thảo '%{title}' đã kết thúc bởi %{actor}" updated: header: "Cuộc họp '%{title}' đã được cập nhật" summary: "Cuộc họp '%{title}' đã được cập nhật bởi %{actor}" @@ -523,8 +523,8 @@ vi: label_agenda_item_move_up: "Di chuyển lên" label_agenda_item_move_down: "Dịch xuống" label_agenda_item_duplicate: "trùng lặp" - label_agenda_item_duplicate_in_next: "Trùng lặp trong lần xuất hiện tiếp theo" - label_agenda_item_duplicate_in_next_title: "Trùng lặp trong lần xuất hiện tiếp theo?" + label_agenda_item_duplicate_in_next: "Lặp lại trong cuộc họp tiếp theo" + label_agenda_item_duplicate_in_next_title: "Có lặp lại trong cuộc họp tiếp theo không?" label_agenda_item_add_notes: "Thêm ghi chú" label_agenda_item_add_outcome: "Thêm kết quả" label_agenda_item_work_package_add: "Thêm gói công việc" diff --git a/modules/meeting/config/locales/crowdin/zh-TW.yml b/modules/meeting/config/locales/crowdin/zh-TW.yml index b6e0204e42f..1dd793057e8 100644 --- a/modules/meeting/config/locales/crowdin/zh-TW.yml +++ b/modules/meeting/config/locales/crowdin/zh-TW.yml @@ -226,9 +226,9 @@ zh-TW: header: "取消:會議 '%{title}'" header_occurrence: "取消:會議發起 '%{title}'" header_series: "取消:系列會議 '%{title}'" - summary_occurrence: "An occurrence of '%{title}' has been cancelled by %{actor}, or you have been removed as a participant" - summary_series: "Meeting series '%{title}' has been cancelled by %{actor}, or you have been removed as a participant" - summary: "'%{title}' has been cancelled by %{actor}, or you have been removed as a participant" + summary_occurrence: "%{actor}已取消「%{title}」的發生,或您已被移除為參與者" + summary_series: "會議系列 '%{title}' 已被 %{actor}取消, 或您已被移除為參與者" + summary: "%{title}' 已被 %{actor}取消,或者您已被刪除為參賽者" date_time: "排定日期/時間" participant_added: header: "會議 '%{title}' - 與會者" @@ -242,7 +242,7 @@ zh-TW: summary_series: "%{actor} 從會議系列中移除 %{participant} '%{title}'" ended: header_series: "結束:會議系列 '%{title}'" - summary_series: "Meeting series '%{title}' has been ended by %{actor}" + summary_series: "會議系列 '%{title}' 已由 %{actor}結束" updated: header: "會議 '%{title}' 已更新" summary: "會議 '%{title}' 已由 %{actor}更新" @@ -523,8 +523,8 @@ zh-TW: label_agenda_item_move_up: "向上移 " label_agenda_item_move_down: "向下移 " label_agenda_item_duplicate: "複製" - label_agenda_item_duplicate_in_next: "Duplicate in next meeting" - label_agenda_item_duplicate_in_next_title: "Duplicate in next meeting?" + label_agenda_item_duplicate_in_next: "在下次會議中重複" + label_agenda_item_duplicate_in_next_title: "在下次會議中重複?" label_agenda_item_add_notes: "新增註記" label_agenda_item_add_outcome: "新增結果" label_agenda_item_work_package_add: "新增工作套件" diff --git a/modules/openid_connect/config/locales/crowdin/tr.yml b/modules/openid_connect/config/locales/crowdin/tr.yml index 6a632251727..e63fc3630b1 100644 --- a/modules/openid_connect/config/locales/crowdin/tr.yml +++ b/modules/openid_connect/config/locales/crowdin/tr.yml @@ -131,7 +131,7 @@ tr: label_configuration_details: Metaveri label_client_details: İstemci detayları label_attribute_mapping: Nitelik ilişkilendirme - notice_created: A new OpenID provider was successfully created. + notice_created: Yeni bir OpenID sağlayıcısı başarıyla oluşturuldu. client_details_description: Configuration details of OpenProject as an OIDC client no_results_table: Henüz bir sağlayıcı tanımlanmadı. plural: OpenID sağlayıcıları diff --git a/modules/overviews/config/locales/crowdin/ja.yml b/modules/overviews/config/locales/crowdin/ja.yml index 537cbfb7540..5f05c63aba5 100644 --- a/modules/overviews/config/locales/crowdin/ja.yml +++ b/modules/overviews/config/locales/crowdin/ja.yml @@ -1,5 +1,5 @@ ja: overviews: - label_home: "%{workspace_type} home" + label_home: "%{workspace_type}の ホーム" label_dashboard: "ダッシュボード" label_overview: "概要" diff --git a/modules/recaptcha/config/locales/crowdin/tr.yml b/modules/recaptcha/config/locales/crowdin/tr.yml index d8a72b0ef95..694dae75089 100644 --- a/modules/recaptcha/config/locales/crowdin/tr.yml +++ b/modules/recaptcha/config/locales/crowdin/tr.yml @@ -2,7 +2,7 @@ tr: plugin_openproject_recaptcha: name: "OpenProject ReCaptcha" - description: "Bu modül giriş sırasında recaptcha kontrolleri sağlar." + description: "Bu modül giriş sırasında recaptcha kontrolleri yapılmasını sağlar." recaptcha: label_recaptcha: "reCAPTCHA" button_please_wait: 'Lütfen bekleyin ...' diff --git a/modules/reporting/config/locales/crowdin/vi.yml b/modules/reporting/config/locales/crowdin/vi.yml index 0b864f3df4a..43bd0a017d5 100644 --- a/modules/reporting/config/locales/crowdin/vi.yml +++ b/modules/reporting/config/locales/crowdin/vi.yml @@ -73,7 +73,7 @@ vi: label_group_by: "Nhóm theo" label_group_by_add: "Thêm thuộc tính theo nhóm" label_inactive: "«không hoạt động»" - label_no: "không" + label_no: "Không" label_none: "(không có dữ liệu)" label_no_reports: "Chưa có báo cáo chi phí." label_report: "Báo cáo" diff --git a/modules/storages/config/locales/crowdin/es.yml b/modules/storages/config/locales/crowdin/es.yml index eab4a0e95de..8e66eb7b4ce 100644 --- a/modules/storages/config/locales/crowdin/es.yml +++ b/modules/storages/config/locales/crowdin/es.yml @@ -104,20 +104,20 @@ es: create_folder: 'Creación de carpetas de proyecto gestionadas:' ensure_root_folder_permissions: 'Establecer permisos de carpeta base:' hide_inactive_folders: 'Ocultar paso de carpetas inactivas:' - remote_folders: 'Read contents of the team folder:' + remote_folders: 'Leer el contenido de la carpeta del equipo:' remove_user_from_group: 'Eliminar usuario del grupo:' rename_project_folder: 'Cambiar el nombre de la carpeta del proyecto gestionado:' one_drive_sync_service: create_folder: 'Creación de carpetas de proyecto gestionadas:' ensure_root_folder_permissions: 'Establecer permisos de carpeta base:' hide_inactive_folders: 'Ocultar paso de carpetas inactivas:' - remote_folders: 'Read contents of the drive root folder:' + remote_folders: 'Leer el contenido de la carpeta raíz de la unidad:' rename_project_folder: 'Cambiar el nombre de la carpeta del proyecto gestionado:' sharepoint_sync_service: create_folder: 'Creación de carpetas de proyecto gestionadas:' ensure_root_folder_permissions: 'Establecer permisos de carpeta base:' hide_inactive_folders: 'Ocultar paso de carpetas inactivas:' - remote_folders: 'Read contents of the drive root folder:' + remote_folders: 'Leer el contenido de la carpeta raíz de la unidad:' rename_project_folder: 'Cambiar el nombre de la carpeta del proyecto gestionado:' errors: messages: @@ -140,7 +140,7 @@ es: conflict: La carpeta %{folder_name} ya existe en %{parent_location}. not_found: "No se encontró %{parent_location}." ensure_root_folder_permissions: - not_found: "%{group_folder} wasn't found. Please check your Nextcloud Team Folder setup." + not_found: "No se ha encontrado %{group_folder}. Compruebe la configuración de su carpeta de equipo de Nextcloud." permission_not_set: no se han podido establecer permisos en %{group_folder}. hide_inactive_folders: permission_not_set: no se han podido establecer permisos en %{path}. @@ -230,7 +230,7 @@ es: storage_delete_result_3: La carpeta del proyecto gestionada automáticamente y todos los archivos que contiene se eliminarán dependencies: nextcloud: - group_folders_app: Team Folders + group_folders_app: Carpetas de equipo integration_app: Integración OpenProject enabled_in_projects: setup_incomplete_description: Este almacenamiento tiene una configuración incompleta. Complete la configuración antes de habilitarlo en varios proyectos. @@ -277,11 +277,11 @@ es: client_folder_creation: Creación automática de carpetas client_folder_removal: Eliminación automática de carpetas drive_contents: Contenido de la unidad - files_request: Fetching team folder files + files_request: Obteniendo archivos de carpetas de equipo header: Carpetas de proyecto gestionadas automáticamente - team_folder_app: 'Dependency: Team Folders' - team_folder_contents: Team folder content - team_folder_presence: Team folder exists + team_folder_app: 'Dependencia: Carpetas de equipo' + team_folder_contents: Contenido de la carpeta de equipo + team_folder_presence: La carpeta del equipo existe userless_access: Autenticación de solicitudes del lado del servidor authentication: existing_token: Token de usuario @@ -322,8 +322,8 @@ es: nc_oauth_request_not_found: No se ha encontrado el terminal para obtener el usuario actualmente conectado. Consulte los registros del servidor para obtener más información. nc_oauth_request_unauthorized: El usuario actual no está autorizado a acceder al almacenamiento remoto de archivos. Consulte los registros del servidor para obtener más información. nc_oauth_token_missing: OpenProject no puede probar la comunicación a nivel de usuario con Nextcloud, ya que el usuario aún no ha vinculado su cuenta de Nextcloud. - nc_team_folder_not_found: The team folder could not be found. - nc_unexpected_content: Unexpected content found in the managed team folder. + nc_team_folder_not_found: No se ha podido encontrar la carpeta del equipo. + nc_unexpected_content: Se ha encontrado contenido inesperado en la carpeta del equipo gestionada. nc_userless_access_denied: La contraseña configurada de la aplicación no es válida. not_configured: No se ha podido validar la conexión. Por favor, termine primero la configuración. od_client_cant_delete_folder: El cliente tiene problemas para eliminar carpetas. Consulte la documentación de configuración de su almacenamiento. diff --git a/modules/storages/config/locales/crowdin/ja.yml b/modules/storages/config/locales/crowdin/ja.yml index 51e942147b9..4708d197140 100644 --- a/modules/storages/config/locales/crowdin/ja.yml +++ b/modules/storages/config/locales/crowdin/ja.yml @@ -533,7 +533,7 @@ ja: name: シェアポイント name_placeholder: 例:シェアポイント show_attachments_toggle: - description: このオプションを無効にすると、作業パッケージのファイルタブの添付ファイルリストが非表示になります。ワークパッケージの説明に添付されたファイルは、内部添付ファイルストレージにアップロードされます。 + description: このオプションを無効にすると、ワークパッケージのファイルタブの添付ファイルリストが非表示になります。ワークパッケージの説明に添付されたファイルは、内部添付ファイルストレージにアップロードされます。 label: ワークパッケージファイルタブに添付ファイルを表示 storage_audience: documentation_intro: アイデンティティプロバイダの以下のオプションと設定については、当社のドキュメントをお読みください。 diff --git a/modules/storages/config/locales/crowdin/pt-BR.yml b/modules/storages/config/locales/crowdin/pt-BR.yml index b6fee81e08c..89796811996 100644 --- a/modules/storages/config/locales/crowdin/pt-BR.yml +++ b/modules/storages/config/locales/crowdin/pt-BR.yml @@ -104,20 +104,20 @@ pt-BR: create_folder: 'Criação de pastas gerenciadas para projetos:' ensure_root_folder_permissions: 'Definir permissões da pasta base:' hide_inactive_folders: 'Etapa "Ocultar pastas inativas":' - remote_folders: 'Read contents of the team folder:' + remote_folders: 'Ler conteúdo da pasta da equipe:' remove_user_from_group: 'Remover usuário do grupo:' rename_project_folder: 'Renomear pasta do projeto gerido:' one_drive_sync_service: create_folder: 'Criação de pastas gerenciadas para projetos:' ensure_root_folder_permissions: 'Definir permissões da pasta base:' hide_inactive_folders: 'Etapa "Ocultar pastas inativas":' - remote_folders: 'Read contents of the drive root folder:' + remote_folders: 'Ler conteúdo da pasta raiz no drive:' rename_project_folder: 'Renomear pasta do projeto gerido:' sharepoint_sync_service: create_folder: 'Criação de pastas gerenciadas para projetos:' ensure_root_folder_permissions: 'Definir permissões da pasta base:' hide_inactive_folders: 'Etapa "Ocultar pastas inativas":' - remote_folders: 'Read contents of the drive root folder:' + remote_folders: 'Ler conteúdo da pasta raiz no drive:' rename_project_folder: 'Renomear pasta do projeto gerido:' errors: messages: @@ -140,7 +140,7 @@ pt-BR: conflict: A pasta %{folder_name} já existe em %{parent_location}. not_found: "%{parent_location} não foi encontrado." ensure_root_folder_permissions: - not_found: "%{group_folder} wasn't found. Please check your Nextcloud Team Folder setup." + not_found: "%{group_folder} não foi encontrado. Verifique a configuração da sua Pasta da equipe Nextcloud." permission_not_set: não foi possível definir permissões em %{group_folder}. hide_inactive_folders: permission_not_set: não foi possível definir permissões em %{path}. @@ -230,7 +230,7 @@ pt-BR: storage_delete_result_3: A pasta do projeto gerenciada automaticamente e todos os arquivos contidos nela serão excluídos dependencies: nextcloud: - group_folders_app: Team Folders + group_folders_app: Pastas da equipe integration_app: Integração OpenProject enabled_in_projects: setup_incomplete_description: Este armazenamento tem uma configuração incompleta. Conclua a configuração antes de habilitá-la em vários projetos. @@ -277,11 +277,11 @@ pt-BR: client_folder_creation: Criação automática de pasta client_folder_removal: Exclusão automática de pasta drive_contents: Conteúdo da unidade - files_request: Fetching team folder files + files_request: Buscando arquivos da pasta da equipe header: Pastas de projeto gerenciadas automaticamente - team_folder_app: 'Dependency: Team Folders' - team_folder_contents: Team folder content - team_folder_presence: Team folder exists + team_folder_app: 'Dependência: Pastas da equipe' + team_folder_contents: Conteúdo da pasta da equipe + team_folder_presence: A pasta da equipe existe userless_access: Autenticação de solicitações no servidor authentication: existing_token: Token do usuário @@ -322,8 +322,8 @@ pt-BR: nc_oauth_request_not_found: O endpoint para recuperar o usuário conectado não foi encontrado. Verifique os logs do servidor para mais detalhes. nc_oauth_request_unauthorized: O usuário atual não está autorizado a acessar o armazenamento remoto de arquivos. Verifique os logs do servidor para mais informações. nc_oauth_token_missing: O OpenProject não pode testar a comunicação do usuário com o Nextcloud, pois a conta do Nextcloud ainda não foi vinculada. - nc_team_folder_not_found: The team folder could not be found. - nc_unexpected_content: Unexpected content found in the managed team folder. + nc_team_folder_not_found: A pasta da equipe não pôde ser localizada. + nc_unexpected_content: Conteúdo inesperado encontrado na pasta da equipe gerenciado. nc_userless_access_denied: A senha configurada do aplicativo não é válida. not_configured: A conexão não pôde ser validada. Conclua a configuração primeiro. od_client_cant_delete_folder: O cliente está com dificuldades para excluir pastas. Verifique a documentação de configuração do seu armazenamento. diff --git a/modules/storages/config/locales/crowdin/tr.yml b/modules/storages/config/locales/crowdin/tr.yml index 1d4159c761b..c5b2c75ef4a 100644 --- a/modules/storages/config/locales/crowdin/tr.yml +++ b/modules/storages/config/locales/crowdin/tr.yml @@ -544,7 +544,7 @@ tr: label: Use access token obtained during user log in manual: helptext: OpenProject will exchange a token with the identity provider for the given audience. - label: Manually specify audience for which to exchange access token (Recommended) + label: Access token değişimi için audience değerini manuel olarak belirleyin (Önerilir) storage_list_blank_slate: description: Onları burada görmek için bir depolama ekleyin. heading: Henüz hiç depolamanız yok. diff --git a/modules/storages/config/locales/crowdin/zh-CN.yml b/modules/storages/config/locales/crowdin/zh-CN.yml index 32c11f78e9c..e227630b5e2 100644 --- a/modules/storages/config/locales/crowdin/zh-CN.yml +++ b/modules/storages/config/locales/crowdin/zh-CN.yml @@ -140,13 +140,13 @@ zh-CN: conflict: 文件夹 %{folder_name} 已经存在于 %{parent_location} 上。 not_found: "未找到 %{parent_location} 。" ensure_root_folder_permissions: - not_found: "未找到 %{group_folder}。请检查您的 Nextcloud 团队文件夹设置。" + not_found: "找不到“%{group_folder}”。请检查您的 Nextcloud 团队文件夹设置。" permission_not_set: 无法设置 %{group_folder} 上的权限。 hide_inactive_folders: permission_not_set: 无法设置 %{path} 上的权限。 remote_folders: not_allowed: '%{username} 无法访问 %{group_folder} 。请检查 Nextcloud 上的文件夹权限。' - not_found: "未找到 %{group_folder} 。请检查您的 Nextcloud 设置。" + not_found: "找不到“%{group_folder}” 。请检查您的 Nextcloud 设置。" remove_user_from_group: conflict: '由于以下原因,无法从 %{group} 组中删除用户 %{user} : %{reason}' failed_to_remove: '由于以下原因,无法从 %{group} 组中删除用户 %{user} : %{reason}' @@ -279,7 +279,7 @@ zh-CN: drive_contents: 驱动器内容 files_request: 正在获取团队文件夹文件 header: 自动托管的项目文件夹 - team_folder_app: '依赖项:团队文件夹' + team_folder_app: '依赖关系:团队文件夹' team_folder_contents: 团队文件夹内容 team_folder_presence: 团队文件夹已存在 userless_access: 服务器端请求身份验证 @@ -320,7 +320,7 @@ zh-CN: nc_oauth_request_not_found: 未找到获取当前连接用户的端点。请检查服务器日志以获取更多信息。 nc_oauth_request_unauthorized: 当前用户无权访问远程文件存储。请检查服务器日志以获取更多信息。 nc_oauth_token_missing: OpenProject 无法测试用户与 Nextcloud 之间的通信,因为用户尚未链接他们的 Nextcloud 帐户。 - nc_team_folder_not_found: 无法找到团队文件夹。 + nc_team_folder_not_found: 找不到该团队文件夹。 nc_unexpected_content: 在受管理的团队文件夹中找到非预期内容。 nc_userless_access_denied: 已配置的应用密码无效。 not_configured: 无法验证连接。请先完成配置。 diff --git a/modules/two_factor_authentication/config/locales/crowdin/ja.yml b/modules/two_factor_authentication/config/locales/crowdin/ja.yml index 80733ae7523..00b3cdb8b81 100644 --- a/modules/two_factor_authentication/config/locales/crowdin/ja.yml +++ b/modules/two_factor_authentication/config/locales/crowdin/ja.yml @@ -40,7 +40,7 @@ ja: label_pwd_confirmation: "パスワード" notice_pwd_confirmation: "これらの設定を変更するにはパスワードを確認する必要があります。" label_device_type: "デバイスタイプ" - label_default_device: "規定の2FAデバイス" + label_default_device: "デフォルトの2FAデバイス" label_device: "2FAデバイス" label_devices: "2FAデバイス" label_one_time_password: "ワンタイムパスワード" @@ -49,8 +49,8 @@ ja: text_otp_delivery_message_sms: "あなたの %{app_title} のワンタイムパスワードは %{token} です" text_otp_delivery_message_voice: "あなたの %{app_title} のワンタイムパスワード: %{pause} %{token}。 %{pause} 繰り返します: %{pause} %{token}" text_enter_2fa: "あなたのデバイスからワンタイムパスワードを入力してください。" - text_2fa_enabled: "ログインごとに、規定の2FAデバイスからOTPトークンを入力するよう要求されます。" - text_2fa_disabled: "2要素証を有効にするには、上記のボタンを使用して新しい2FAデバイスを登録します。既にデバイスがある場合は、規定にする必要があります。" + text_2fa_enabled: "ログインごとに、デフォルトの2FAデバイスからOTPトークンを入力するよう要求されます。" + text_2fa_disabled: "2要素証を有効にするには、上記のボタンを使用して新しい2FAデバイスを登録します。既にデバイスがある場合は、デフォルトにする必要があります。" login: enter_backup_code_title: バックアップコードを入力 enter_backup_code_text: 登録された2FAデバイスにアクセスできない場合は、コードのリストから有効なバックアップコードを入力してください。 @@ -101,23 +101,23 @@ ja: devices: add_new: "新しい2FAデバイスを追加します" register: "デバイスを登録する" - confirm_default: "規定のデバイスの変更を確認する" + confirm_default: "デフォルトのデバイスの変更を確認する" confirm_device: "デバイスを確認" confirm_now: "確認されていません、ここをクリックして有効にする" cannot_delete_default: "既定のデバイスを削除できません" - make_default_are_you_sure: "この2FAデバイスを規定にしてもよろしいですか?" - make_default_failed: "規定の2FAデバイスの更新に失敗しました。" + make_default_are_you_sure: "この2FAデバイスをデフォルトにしてもよろしいですか?" + make_default_failed: "デフォルトの2FAデバイスの更新に失敗しました。" deletion_are_you_sure: "この2FAデバイスを削除してもよろしいですか?" registration_complete: "2FAデバイスの登録が完了しました!" registration_failed_token_invalid: "2FAデバイスの登録に失敗しました。トークンが無効でした。" registration_failed_update: "2FAデバイスの登録に失敗しました。トークンは有効でしたがデバイスを更新できませんでした。" confirm_send_failed: "2FAデバイスの確認に失敗しました。" button_complete_registration: "2FAの登録を完了する" - text_confirm_to_complete_html: "規定のデバイスからワンタイムパスワードを入力して、デバイス %{identifier} の登録を完了してください。" - text_confirm_to_change_default_html: "現在の規定のデバイスからワンタイムパスワードを入力して、規定のデバイスを %{new_identifier} に変更してください。" + text_confirm_to_complete_html: "デフォルトのデバイスからワンタイムパスワードを入力して、デバイス %{identifier} の登録を完了してください。" + text_confirm_to_change_default_html: "現在のデフォルトのデバイスからワンタイムパスワードを入力して、デフォルトのデバイスを %{new_identifier} に変更してください。" text_identifier: "このフィールドを使用してデバイスにカスタムIDを与えることができます。" failed_to_delete: "2FAデバイスの削除に失敗しました。" - is_default_cannot_delete: "デバイスは規定になっているため、アクティブなセキュリティポリシーのため削除できません。削除する前に別のデバイスを規定にしてください。" + is_default_cannot_delete: "デバイスはデフォルトになっているため、アクティブなセキュリティポリシーのため削除できません。削除する前に別のデバイスをデフォルトにしてください。" not_existing: "アカウントに2FAデバイスが登録されていません。" 2fa_from_input: 確認するには、 %{device_name} のコードを入力してください。 2fa_from_webauthn: WebAuthn デバイス %{device_name}を挿入してください。 それがUSBベースの場合は、それを接続し、それをタッチしてください。 From 427731612d7f6e1ec671a2e05ffa35ec2891d6f6 Mon Sep 17 00:00:00 2001 From: OpenProject Actions CI Date: Sun, 8 Feb 2026 03:59:55 +0000 Subject: [PATCH 173/293] update locales from crowdin [ci skip] --- config/locales/crowdin/ja.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/crowdin/ja.yml b/config/locales/crowdin/ja.yml index 280aabc0208..d7f36305fb7 100644 --- a/config/locales/crowdin/ja.yml +++ b/config/locales/crowdin/ja.yml @@ -2248,7 +2248,7 @@ ja: create_project: attributes_heading: "Fill in this mandatory information to work on your projects." template_label: "テンプレートを使用" - template_heading: "Select a project template to work with the most common project management methods, or create a project from scratch." + template_heading: "テンプレートを選択するか、ゼロからプロジェクトを作成。" copy_options: dependencies_label: "テンプレートからコピー" blank_template: From 71508a2aa58e888609b027403bf76c5faf4fa21d Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Fri, 6 Feb 2026 20:12:47 -0300 Subject: [PATCH 174/293] Remove skipped feature, Flesh out Stories feature --- .../features/backlogs/change_status_spec.rb | 146 ------------------ .../spec/features/stories_in_backlog_spec.rb | 27 +++- .../backlogs/spec/support/pages/backlogs.rb | 138 +++++------------ 3 files changed, 57 insertions(+), 254 deletions(-) delete mode 100644 modules/backlogs/spec/features/backlogs/change_status_spec.rb diff --git a/modules/backlogs/spec/features/backlogs/change_status_spec.rb b/modules/backlogs/spec/features/backlogs/change_status_spec.rb deleted file mode 100644 index 9077e4230ad..00000000000 --- a/modules/backlogs/spec/features/backlogs/change_status_spec.rb +++ /dev/null @@ -1,146 +0,0 @@ -# frozen_string_literal: true - -#-- copyright -# OpenProject is an open source project management software. -# Copyright (C) the OpenProject GmbH -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License version 3. -# -# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: -# Copyright (C) 2006-2013 Jean-Philippe Lang -# Copyright (C) 2010-2013 the ChiliProject Team -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# See COPYRIGHT and LICENSE files for more details. -#++ - -require "spec_helper" -require_relative "../../support/pages/backlogs" - -RSpec.describe.skip "Backlogs change status", :js do - shared_let(:story_type) { create(:type_feature) } - shared_let(:task_type) { create(:type_task) } - shared_let(:project) { create(:project, types: [story_type, task_type]) } - shared_let(:role) do - create(:project_role, - permissions: %i[edit_work_packages - change_work_package_status - view_master_backlog - view_work_packages]) - end - - shared_let(:user) do - create(:user, - member_with_roles: { project => role }) - end - shared_let(:sprint) do - create(:version, - project:, - name: "Sprint") - end - shared_let(:new_status) { create(:default_status, name: "New") } - shared_let(:in_progress_status) { create(:status, name: "In progress") } - shared_let(:default_priority) { create(:default_priority) } - shared_let(:story) do - create(:work_package, - type: story_type, - project:, - status: new_status, - priority: default_priority, - story_points: 3, - version: sprint) - end - shared_let(:workflow) do - create(:workflow, - old_status: new_status, - new_status: in_progress_status, - role:, - type: story_type) - end - - let(:backlogs_page) { Pages::Backlogs.new(project) } - - before do - allow(Setting) - .to receive(:plugin_openproject_backlogs) - .and_return("story_types" => [story_type.id.to_s], - "task_type" => task_type.id.to_s) - login_as(user) - end - - def expect_fields(enabled: [], disabled: [], none: []) - enabled.each do |field| - expect(page).to have_field(WorkPackage.human_attribute_name(field)) - end - - disabled.each do |field| - expect(page).to have_field(WorkPackage.human_attribute_name(field), disabled: true) - end - - none.each do |field| - expect(page).to have_no_field(WorkPackage.human_attribute_name(field), visible: :all) - end - end - - # this test acts as a control for the other tests in this file as it's easy to - # expect a field to not be present, and have the test still pass when the - # field is renamed. - context "when the user has edit_work_packages permission" do - it "is possible to edit any story field" do - backlogs_page.visit! - backlogs_page.enter_edit_story_mode(story) - - expect_fields(enabled: %i[type subject status story_points]) - - backlogs_page.alter_attributes_in_edit_story_mode(story, subject: "Hello subject") - backlogs_page.save_story_from_edit_mode(story) - - expect(story.reload.subject).to eq("Hello subject") - end - end - - context "when the user has only change_work_package_status permission" do - before do - RolePermission.where(permission: "edit_work_packages").delete_all - end - - it "is only possible to edit status field of stories" do - backlogs_page.visit! - backlogs_page.enter_edit_story_mode(story, text: story.status.name) - - expect_fields(enabled: %i[status], disabled: %i[type subject story_points]) - - backlogs_page.alter_attributes_in_edit_story_mode(story, status: in_progress_status.name) - backlogs_page.save_story_from_edit_mode(story) - - expect(story.reload.status).to eq(in_progress_status) - end - end - - context "when the user has neither change_work_package_status nor edit_work_packages permission" do - before do - RolePermission.where(permission: ["change_work_package_status", "edit_work_packages"]).delete_all - end - - it "is not possible to edit any story field" do - backlogs_page.visit! - backlogs_page.enter_edit_story_mode(story) - - expect_fields(none: %i[type subject status story_points]) - end - end -end diff --git a/modules/backlogs/spec/features/stories_in_backlog_spec.rb b/modules/backlogs/spec/features/stories_in_backlog_spec.rb index 5cc482d6688..41e433b1191 100644 --- a/modules/backlogs/spec/features/stories_in_backlog_spec.rb +++ b/modules/backlogs/spec/features/stories_in_backlog_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH @@ -37,7 +39,7 @@ RSpec.describe "Stories in backlog", :js, enabled_module_names: %w(work_package_tracking backlogs)) end let!(:story) { create(:type_feature) } - let!(:other_story) { create(:type) } + let!(:other_story) { create(:type, name: "Story") } let!(:task) { create(:type_task) } let!(:priority) { create(:default_priority) } let!(:default_status) { create(:status, is_default: true) } @@ -69,7 +71,7 @@ RSpec.describe "Stories in backlog", :js, status: default_status, version: sprint, position: 1, - story_points: 10) + story_points: 8) end let!(:sprint_story1_task) do create(:work_package, @@ -92,7 +94,7 @@ RSpec.describe "Stories in backlog", :js, status: default_status, version: sprint, position: 2, - story_points: 20) + story_points: 13) end let!(:backlog_story1) do create(:work_package, @@ -104,8 +106,8 @@ RSpec.describe "Stories in backlog", :js, let!(:sprint) do create(:version, project:, - start_date: Date.today - 10.days, - effective_date: Date.today + 10.days, + start_date: Time.zone.today - 10.days, + effective_date: Time.zone.today + 10.days, version_settings_attributes: [{ project:, display: VersionSetting::DISPLAY_LEFT }]) end let!(:backlog) do @@ -127,7 +129,7 @@ RSpec.describe "Stories in backlog", :js, type: story, status: default_status, version: sprint, - story_points: 10) + story_points: 5) end let(:backlogs_page) { Pages::Backlogs.new(project) } @@ -139,7 +141,7 @@ RSpec.describe "Stories in backlog", :js, "task_type" => task.id.to_s) end - it "displays stories which are editable" do + it "displays stories which are editable via details view" do backlogs_page.visit! # All stories are visible in their sprint/backlog @@ -166,7 +168,16 @@ RSpec.describe "Stories in backlog", :js, .expect_stories_in_order(sprint, sprint_story1, sprint_story2) # Velocity is calculated by summing up all story points in a sprint + backlogs_page.expect_velocity(sprint, 21) + backlogs_page - .expect_velocity(sprint, 30) + .edit_story_in_details_view(sprint_story1, story_points: 5) + + backlogs_page.expect_velocity(sprint, 18) + + backlogs_page + .edit_story_in_details_view(sprint_story2, subject: "Updated story", story_points: 3) + + backlogs_page.expect_velocity(sprint, 8) end end diff --git a/modules/backlogs/spec/support/pages/backlogs.rb b/modules/backlogs/spec/support/pages/backlogs.rb index c41a5cbc19e..0674fe8398d 100644 --- a/modules/backlogs/spec/support/pages/backlogs.rb +++ b/modules/backlogs/spec/support/pages/backlogs.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH @@ -37,42 +39,25 @@ module Pages @project = project end - def enter_edit_story_mode(story, text: nil) - text ||= story.subject - within_story(story) do - find(:css, ".editable", text:).click - end - end - def enter_edit_backlog_mode(backlog) within_backlog_menu(backlog) do |menu| menu.find(:menuitem, "Edit sprint").click end end - def alter_attributes_in_edit_story_mode(story, attributes) - edit_proc = ->(*) do + def alter_attributes_in_details_view(story, **attributes) + within_details_view(story) do |details_view| attributes.each do |key, value| - field_name = WorkPackage.human_attribute_name(key) - case key - when :subject, :story_points - fill_in field_name, with: value.to_s - when :status, :type - select value.to_s, from: field_name - else - raise NotImplementedError - end - end - end + details_view + .edit_field(key.to_s.camelize(:lower)) + .update(value) # rubocop:disable Rails/SaveBang - if story - within_story(story, &edit_proc) - else - edit_proc.call + details_view.expect_and_dismiss_toaster message: "Successful update." + end end end - def alter_attributes_in_edit_backlog_mode(backlog, attributes) + def alter_attributes_in_edit_backlog_mode(backlog, **attributes) within_backlog(backlog) do attributes.each do |key, value| case key @@ -89,48 +74,26 @@ module Pages end end - def save_story_from_edit_mode(story) - save_proc = ->(*) do - field = find_field(disabled: false, match: :first) - keys = [:return] - keys << :return if field.tag_name == "select" # select field needs a second return key sent for some reason - field.send_keys(*keys) - - expect(page).to have_no_field(WorkPackage.human_attribute_name(:subject)) - end - - if story - within_story(story, &save_proc) - else - save_proc.call - end - wait_for_save_completion - end - def save_backlog_from_edit_mode(backlog) within_backlog(backlog) do find_field("Name").send_keys :return end end - def wait_for_save_completion - expect(page).to have_no_css(".ajax-indicator") - end - - def edit_backlog(backlog, attributes) + def edit_backlog(backlog, **attributes) enter_edit_backlog_mode(backlog) - alter_attributes_in_edit_backlog_mode(backlog, attributes) + alter_attributes_in_edit_backlog_mode(backlog, **attributes) save_backlog_from_edit_mode(backlog) end - def edit_story(story, attributes) - enter_edit_story_mode(story) + def edit_story_in_details_view(story, **attributes) + click_in_story_menu(story, "Open details view") - alter_attributes_in_edit_story_mode(story, attributes) + expect(page).to have_current_path details_backlogs_project_backlogs_path(story.project, story) - save_story_from_edit_mode(story) + alter_attributes_in_details_view(story, **attributes) end def click_in_backlog_menu(backlog, item_name) @@ -139,12 +102,17 @@ module Pages end end + def click_in_story_menu(story, item_name) + within_story_menu(story) do |menu| + menu.find(:menuitem, text: item_name).click + end + end + def drag_in_sprint(moved, target, before: true) moved_element = find(story_selector(moved)) target_element = find(story_selector(target)) drag_n_drop_element from: moved_element, to: target_element, offset_x: 0, offset_y: before ? -5 : +10 - wait_for_save_completion end def fold_backlog(backlog) @@ -177,40 +145,6 @@ module Pages end end - def expect_for_story(story, attributes) - within_story(story) do - attributes.each do |key, value| - case key - when :subject - expect(page) - .to have_css("div.subject", text: value) - when :status - expect(page) - .to have_css("div.status_id", text: value) - when :type - expect(page) - .to have_css("div.type_id", text: value) - else - raise NotImplementedError - end - end - end - end - - def expect_story_link_to_wp_page(story) - within_story(story) do - expect(page) - .to have_link(story.to_param, href: work_package_path(story)) - end - end - - def expect_status_options(story, statuses) - within_story(story) do - expect(all(".status_id option").map { |n| n.text.strip }) - .to match_array(statuses.map(&:name)) - end - end - def expect_velocity(backlog, velocity) within("#backlog_#{backlog.id} .velocity") do expect(page) @@ -228,18 +162,6 @@ module Pages end end - def expect_in_backlog_menu(backlog, item_name) - within_backlog(backlog) do - find(".header .menu-trigger").click - - expect(page) - .to have_css(".header .backlog-menu .item", text: item_name) - - # Close it again for next test - find(".header .menu-trigger").click - end - end - def expect_and_dismiss_error(message) expect(page).to have_content message @@ -258,6 +180,22 @@ module Pages end end + def within_story_menu(story, &) + within_story(story) do + find(:button, accessible_name: "Story actions").click + + within(:menu, &) + end + end + + def within_details_view(story, &) + details_view = Pages::PrimerizedSplitWorkPackage.new(story) + details_view.expect_tab :overview + details_view.expect_subject + + yield details_view + end + private def within_story(story, &) From 324213ed2e49f889261027cf4535db7dae52087e Mon Sep 17 00:00:00 2001 From: indu shekar Date: Sun, 8 Feb 2026 23:42:08 +0530 Subject: [PATCH 175/293] Fix grammar errors in docs README (#21909) Fix typo in docs README Changes "you installation" to "your installation" in operation section. Changes "The guides" to "The guides for" in installation section. --- docs/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/README.md b/docs/README.md index 2f78a0c54ff..e0746b963e1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -14,7 +14,7 @@ keywords: help, documentation Get started with installing and upgrading OpenProject using [our Installation Guide starting point](./installation-and-operations/). -The guides [packaged](./installation-and-operations/installation/packaged) and [Docker-based](./installation-and-operations/installation/docker) installations are provided. +The guides for [packaged](./installation-and-operations/installation/packaged) and [Docker-based](./installation-and-operations/installation/docker) installations are provided. ## Upgrading @@ -24,7 +24,7 @@ The guides for [upgrading](./installation-and-operations/operation/upgrading) ar ## Operation -* [Backing up you installation](./installation-and-operations/operation/backing-up) +* [Backing up your installation](./installation-and-operations/operation/backing-up) * [Alter configuration of OpenProject](./installation-and-operations/configuration) * [Manual repository integration for Git and Subversion](./installation-and-operations/configuration/repositories) * [Configure incoming mails](./installation-and-operations/configuration/incoming-emails) From 04e6dff4c0050854e9e71677213c7da7b8e8bb89 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Sun, 8 Feb 2026 16:25:35 -0300 Subject: [PATCH 176/293] [#71351] Add placeholder to date pickers Add `placeholder` `@Input` to both `OpBasicSingleDatePickerComponent` and `OpBasicRangeDatePickerComponent`, and pass it through from the server-rendered ERB template via `angular_component_tag`. For the range picker on mobile, the same placeholder is used for both start and end date fields. Default `placeholder` to an empty string when not provided, to avoid `angular_component_tag` serializing `nil` as `"null"`. Co-Authored-By: Claude Opus 4.6 --- .../basic-range-date-picker.component.html | 3 +++ .../basic-range-date-picker.component.ts | 2 ++ .../basic-single-date-picker.component.html | 2 ++ .../basic-single-date-picker.component.ts | 2 ++ lib/primer/open_project/forms/date_picker.html.erb | 3 ++- 5 files changed, 11 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/shared/components/datepicker/basic-range-date-picker/basic-range-date-picker.component.html b/frontend/src/app/shared/components/datepicker/basic-range-date-picker/basic-range-date-picker.component.html index b441558a71c..8c320747278 100644 --- a/frontend/src/app/shared/components/datepicker/basic-range-date-picker/basic-range-date-picker.component.html +++ b/frontend/src/app/shared/components/datepicker/basic-range-date-picker/basic-range-date-picker.component.html @@ -13,6 +13,7 @@ [attr.name]="name" [required]="required" [disabled]="disabled" + [placeholder]="placeholder" [ngModel]="stringValue" (ngModelChange)="changeValueFromInputDebounced($event)" (focus)="showDatePicker()" @@ -29,6 +30,7 @@ name="{{ name }}-start" [required]="required" [disabled]="disabled" + [placeholder]="placeholder" [ngModel]="value[0] || ''" (ngModelChange)="changeValueFromInputDebounced([$event, value[1] || ''])" /> @@ -42,6 +44,7 @@ name="{{ name }}-end" [required]="required" [disabled]="disabled" + [placeholder]="placeholder" [ngModel]="value[1] || ''" (ngModelChange)="changeValueFromInputDebounced([value[0] || '', $event])" /> diff --git a/frontend/src/app/shared/components/datepicker/basic-range-date-picker/basic-range-date-picker.component.ts b/frontend/src/app/shared/components/datepicker/basic-range-date-picker/basic-range-date-picker.component.ts index 882a6a9b806..dc707ceafcf 100644 --- a/frontend/src/app/shared/components/datepicker/basic-range-date-picker/basic-range-date-picker.component.ts +++ b/frontend/src/app/shared/components/datepicker/basic-range-date-picker/basic-range-date-picker.component.ts @@ -111,6 +111,8 @@ export class OpBasicRangeDatePickerComponent implements OnInit, ControlValueAcce @Input() disabled = false; + @Input() placeholder = ''; + @Input() minimalDate:Date|null = null; @Input() inputClassNames = ''; diff --git a/frontend/src/app/shared/components/datepicker/basic-single-date-picker/basic-single-date-picker.component.html b/frontend/src/app/shared/components/datepicker/basic-single-date-picker/basic-single-date-picker.component.html index 1b407402ed1..737283ecbb2 100644 --- a/frontend/src/app/shared/components/datepicker/basic-single-date-picker/basic-single-date-picker.component.html +++ b/frontend/src/app/shared/components/datepicker/basic-single-date-picker/basic-single-date-picker.component.html @@ -14,6 +14,7 @@ [attr.name]="name" [required]="required" [disabled]="disabled" + [placeholder]="placeholder" (input)="changeValueFromInput($event.target.value)" (focus)="showDatePicker()" (click)="sentCalendarToTopLayer()" @@ -34,6 +35,7 @@ [attr.name]="name" [required]="required" [disabled]="disabled" + [placeholder]="placeholder" [ngModel]="value" (ngModelChange)="changeValueFromInput($event)" /> diff --git a/frontend/src/app/shared/components/datepicker/basic-single-date-picker/basic-single-date-picker.component.ts b/frontend/src/app/shared/components/datepicker/basic-single-date-picker/basic-single-date-picker.component.ts index 216a47eea12..8265e6969b2 100644 --- a/frontend/src/app/shared/components/datepicker/basic-single-date-picker/basic-single-date-picker.component.ts +++ b/frontend/src/app/shared/components/datepicker/basic-single-date-picker/basic-single-date-picker.component.ts @@ -89,6 +89,8 @@ export class OpBasicSingleDatePickerComponent implements ControlValueAccessor, O @Input() disabled = false; + @Input() placeholder = ''; + @Input() minimalDate:Date|null = null; @Input() inputClassNames = ''; diff --git a/lib/primer/open_project/forms/date_picker.html.erb b/lib/primer/open_project/forms/date_picker.html.erb index 147f06d4284..a9cd1e8f6b0 100644 --- a/lib/primer/open_project/forms/date_picker.html.erb +++ b/lib/primer/open_project/forms/date_picker.html.erb @@ -45,7 +45,8 @@ See COPYRIGHT and LICENSE files for more details. name: @datepicker_options.fetch(:name) { builder.field_name(@input.name) }, value: @datepicker_options.fetch(:value) { @input.input_arguments[:value] || builder.object&.send(@input.name) }, inputClassNames: @datepicker_options.fetch(:class) { @input.input_arguments[:class] }, - dataAction: @datepicker_options.fetch(:data, {}).fetch(:action, nil) + dataAction: @datepicker_options.fetch(:data, {}).fetch(:action, nil), + placeholder: @input.input_arguments.fetch(:placeholder, "") ) %> <% end %> <% end %> From 67cdb49dda687550f4182ca1647a90a7c9bf8386 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Sun, 8 Feb 2026 16:27:09 -0300 Subject: [PATCH 177/293] Add initial spec for DatePicker inputs Co-Authored-By: Claude Opus 4.6 --- .../open_project/forms/date_picker_spec.rb | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 spec/lib/primer/open_project/forms/date_picker_spec.rb diff --git a/spec/lib/primer/open_project/forms/date_picker_spec.rb b/spec/lib/primer/open_project/forms/date_picker_spec.rb new file mode 100644 index 00000000000..0ecafdf8824 --- /dev/null +++ b/spec/lib/primer/open_project/forms/date_picker_spec.rb @@ -0,0 +1,129 @@ +# 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 Primer::OpenProject::Forms::DatePicker, type: :forms do + include ViewComponent::TestHelpers + + let(:model) { build_stubbed(:comment) } + + describe "single date picker" do + def render_form + render_in_view_context(model) do |model| + primer_form_with(url: "/foo", model:) do |f| + render_inline_form(f) do |form| + form.single_date_picker( + name: :some_date, + label: "Some date", + placeholder: "Pick a date", + datepicker_options: { value: "" } + ) + end + end + end + end + + subject(:rendered_form) do + render_form + page + end + + it "renders the label" do + expect(rendered_form).to have_element :label + end + + it "renders the single date picker angular component" do + expect(rendered_form).to have_element "opce-basic-single-date-picker" + end + + it "passes placeholder as a data attribute" do + expect(rendered_form).to have_element "opce-basic-single-date-picker", + "data-placeholder": "Pick a date".to_json + end + end + + describe "without placeholder" do + def render_form + render_in_view_context(model) do |model| + primer_form_with(url: "/foo", model:) do |f| + render_inline_form(f) do |form| + form.single_date_picker( + name: :some_date, + label: "Some date", + datepicker_options: { value: "" } + ) + end + end + end + end + + subject(:rendered_form) do + render_form + page + end + + it "defaults placeholder to an empty string" do + expect(rendered_form).to have_element "opce-basic-single-date-picker", + "data-placeholder": "".to_json + end + end + + describe "range date picker" do + def render_form + render_in_view_context(model) do |model| + primer_form_with(url: "/foo", model:) do |f| + render_inline_form(f) do |form| + form.range_date_picker( + name: :some_date, + label: "Some date", + placeholder: "Pick a range", + datepicker_options: { value: "" } + ) + end + end + end + end + + subject(:rendered_form) do + render_form + page + end + + it "renders the range date picker angular component" do + expect(rendered_form).to have_element "opce-range-date-picker" + end + + it "passes placeholder as a data attribute" do + expect(rendered_form).to have_element "opce-range-date-picker", + "data-placeholder": "Pick a range".to_json + end + end +end From ab76984c5bd70e71cead9df88ccd4b0777699f5f Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Sun, 8 Feb 2026 16:27:24 -0300 Subject: [PATCH 178/293] Make datepicker_options optional Default `datepicker_options` to `{}` in `SingleDatePickerInput` (inherited by `RangeDatePickerInput`), and remove now-redundant empty hash arguments from callsites. Co-Authored-By: Claude Opus 4.6 --- .../open_project/forms/dsl/single_date_picker_input.rb | 2 +- modules/backlogs/app/forms/backlogs/backlog_header_form.rb | 6 ++---- .../lib/primer/open_project/forms/dsl/input_methods_spec.rb | 4 ++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/primer/open_project/forms/dsl/single_date_picker_input.rb b/lib/primer/open_project/forms/dsl/single_date_picker_input.rb index 1337102b571..19195275a16 100644 --- a/lib/primer/open_project/forms/dsl/single_date_picker_input.rb +++ b/lib/primer/open_project/forms/dsl/single_date_picker_input.rb @@ -35,7 +35,7 @@ module Primer class SingleDatePickerInput < Primer::Forms::Dsl::TextFieldInput attr_reader :datepicker_options - def initialize(name:, label:, datepicker_options:, **system_arguments) + def initialize(name:, label:, datepicker_options: {}, **system_arguments) @datepicker_options = derive_datepicker_options(datepicker_options) super(name:, label:, **system_arguments) diff --git a/modules/backlogs/app/forms/backlogs/backlog_header_form.rb b/modules/backlogs/app/forms/backlogs/backlog_header_form.rb index f8b298d9411..65625de8dfd 100644 --- a/modules/backlogs/app/forms/backlogs/backlog_header_form.rb +++ b/modules/backlogs/app/forms/backlogs/backlog_header_form.rb @@ -48,16 +48,14 @@ module Backlogs label: attribute_name(:start_date), placeholder: attribute_name(:start_date), visually_hide_label: true, - leading_visual: { icon: :calendar }, - datepicker_options: {} + leading_visual: { icon: :calendar } ) dates.single_date_picker( name: :effective_date, label: attribute_name(:effective_date), placeholder: attribute_name(:effective_date), visually_hide_label: true, - leading_visual: { icon: :calendar }, - datepicker_options: {} + leading_visual: { icon: :calendar } ) end diff --git a/spec/lib/primer/open_project/forms/dsl/input_methods_spec.rb b/spec/lib/primer/open_project/forms/dsl/input_methods_spec.rb index 032477f00fa..66fefe5292e 100644 --- a/spec/lib/primer/open_project/forms/dsl/input_methods_spec.rb +++ b/spec/lib/primer/open_project/forms/dsl/input_methods_spec.rb @@ -226,14 +226,14 @@ RSpec.describe Primer::OpenProject::Forms::Dsl::InputMethods, type: :forms do end describe "#single_date_picker" do - let(:field_group) { form_dsl.single_date_picker(name:, label:, datepicker_options: {}, **options) } + let(:field_group) { form_dsl.single_date_picker(name:, label:, **options) } include_examples "input class", Primer::OpenProject::Forms::Dsl::SingleDatePickerInput it_behaves_like "supporting help texts" end describe "#range_date_picker" do - let(:field_group) { form_dsl.range_date_picker(name:, label:, datepicker_options: {}, **options) } + let(:field_group) { form_dsl.range_date_picker(name:, label:, **options) } include_examples "input class", Primer::OpenProject::Forms::Dsl::RangeDatePickerInput it_behaves_like "supporting help texts" From fabe7780255b223a0917485712a968bc570e198f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Mon, 9 Feb 2026 12:58:33 +0100 Subject: [PATCH 179/293] Allow requiring login for external link https://community.openproject.org/work_packages/71624 --- .../external_link_warning_controller.rb | 16 +++++++-- .../settings/external_links_settings_form.rb | 4 +++ config/constants/settings/definition.rb | 5 +++ config/locales/en.yml | 3 ++ .../external_link_warning_controller_spec.rb | 25 +++++++++++++ spec/features/external_link_capture_spec.rb | 36 ++++++++++++++++--- 6 files changed, 83 insertions(+), 6 deletions(-) diff --git a/app/controllers/external_link_warning_controller.rb b/app/controllers/external_link_warning_controller.rb index 281e23ebb58..b3f778fce62 100644 --- a/app/controllers/external_link_warning_controller.rb +++ b/app/controllers/external_link_warning_controller.rb @@ -34,19 +34,31 @@ class ExternalLinkWarningController < ApplicationController skip_before_action :check_if_login_required no_authorization_required! :show - before_action :parse_external_url, only: [:show] - before_action :verify_capture_enabled, only: [:show] + before_action :parse_external_url + before_action :verify_capture_enabled + before_action :optional_require_login def show; end private + def login_back_url_params + params.permit(:url) + end + def verify_capture_enabled unless capture_enabled? redirect_to @external_url, allow_other_host: true, status: :see_other end end + def optional_require_login + return unless Setting.capture_external_links? + return unless Setting.capture_external_links_require_login? + + require_login + end + def capture_enabled? Setting.capture_external_links? && EnterpriseToken.allows_to?(:capture_external_links) end diff --git a/app/forms/admin/settings/external_links_settings_form.rb b/app/forms/admin/settings/external_links_settings_form.rb index 946f67c328c..5aa54f233f0 100644 --- a/app/forms/admin/settings/external_links_settings_form.rb +++ b/app/forms/admin/settings/external_links_settings_form.rb @@ -36,6 +36,10 @@ module Admin name: :capture_external_links, caption: I18n.t(:setting_capture_external_links_text) ) + sf.check_box( + name: :capture_external_links_require_login, + caption: I18n.t(:setting_capture_external_links_require_login_text) + ) end end end diff --git a/config/constants/settings/definition.rb b/config/constants/settings/definition.rb index 9ab4a0b1d3c..c9ec3467a1f 100644 --- a/config/constants/settings/definition.rb +++ b/config/constants/settings/definition.rb @@ -1293,6 +1293,11 @@ module Settings description: "Redirect external links through a warning page before leaving the application", default: false, writable: -> { EnterpriseToken.allows_to?(:capture_external_links) } + }, + capture_external_links_require_login: { + description: "Require users to be logged in before being able to navigate to external links", + default: false, + writable: -> { EnterpriseToken.allows_to?(:capture_external_links) } } }.freeze diff --git a/config/locales/en.yml b/config/locales/en.yml index 5e97c227857..45e3cf58243 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -4501,6 +4501,9 @@ en: setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour. diff --git a/spec/controllers/external_link_warning_controller_spec.rb b/spec/controllers/external_link_warning_controller_spec.rb index 520bdf48c3c..9e3da78dfec 100644 --- a/spec/controllers/external_link_warning_controller_spec.rb +++ b/spec/controllers/external_link_warning_controller_spec.rb @@ -73,6 +73,31 @@ RSpec.describe ExternalLinkWarningController do end end + context "when capture is enabled and login is required", + with_ee: %i[capture_external_links], + with_settings: { capture_external_links: true, + capture_external_links_require_login: true } do + context "when logged in" do + current_user { create(:user) } + + it "renders the warning page when logged in" do + get :show, params: { url: "https://example.com" } + + expect(response).to have_http_status(:success) + expect(response.body).to include("Leaving OpenProject") + expect(response.body).to include("https://example.com") + end + end + + context "when not logged in" do + it "redirects to login" do + get :show, params: { url: "https://example.com" } + back_url = external_redirect_url(url: "https://example.com") + expect(response).to redirect_to(signin_path(back_url:)) + end + end + end + context "with an invalid URL" do it "redirects to home when URL is blank" do get :show, params: { url: "" } diff --git a/spec/features/external_link_capture_spec.rb b/spec/features/external_link_capture_spec.rb index d54a768f973..0e964b76618 100644 --- a/spec/features/external_link_capture_spec.rb +++ b/spec/features/external_link_capture_spec.rb @@ -34,7 +34,7 @@ RSpec.describe "External link capture", :js, :selenium do shared_let(:admin) { create(:admin) } let(:project) { create(:project, enabled_module_names: %w[wiki]) } - let(:external_url) { "https://www.openprojet.org/" } + let(:external_url) { "https://www.openproject.org/" } let!(:wiki_page) do create(:wiki_page, wiki: project.wiki, @@ -43,9 +43,7 @@ RSpec.describe "External link capture", :js, :selenium do text: %(A link to OpenProject.)) end - before do - login_as(admin) - end + current_user { admin } shared_examples "opens external link directly in a new window" do it "keeps the default external link behaviour" do @@ -100,6 +98,36 @@ RSpec.describe "External link capture", :js, :selenium do # Ignore errors from already-closed windows/tabs end end + + context "when not logged in, but required", + with_settings: { capture_external_links: true, + capture_external_links_require_login: true } do + current_user { User.anonymous } + + it "redirects to login page if not logged in" do + visit external_redirect_path(url: external_url) + expect(page.current_url).to include("/login") + expect(page).to have_no_text I18n.t("external_link_warning.title") + expect(page).to have_no_text I18n.t("external_link_warning.warning_message") + expect(page).to have_no_text I18n.t("external_link_warning.continue_message") + + expect(page).to have_no_link(I18n.t("external_link_warning.continue_button"), href: external_url) + end + end + + context "when not logged in and not required", + with_settings: { capture_external_links: true, + capture_external_links_require_login: false } do + it "shows the external link warning" do + visit external_redirect_path(url: external_url) + expect(page.current_url).to include("/external_redirect") + expect(page).to have_text I18n.t("external_link_warning.title") + expect(page).to have_text I18n.t("external_link_warning.warning_message") + expect(page).to have_text I18n.t("external_link_warning.continue_message") + + expect(page).to have_link(I18n.t("external_link_warning.continue_button"), href: external_url) + end + end end context "when no enterprise token is present" do From cfdaacdb8015635ed101ca670aefdac09337f625 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Feb 2026 10:49:41 -0300 Subject: [PATCH 180/293] Bump pullpreview/action from 5 to 6 (#21913) Bumps [pullpreview/action](https://github.com/pullpreview/action) from 5 to 6. - [Release notes](https://github.com/pullpreview/action/releases) - [Changelog](https://github.com/pullpreview/action/blob/master/CHANGELOG.md) - [Commits](https://github.com/pullpreview/action/compare/v5...v6) --- updated-dependencies: - dependency-name: pullpreview/action dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/pullpreview.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pullpreview.yml b/.github/workflows/pullpreview.yml index 6e909c68c1e..63990de45fc 100644 --- a/.github/workflows/pullpreview.yml +++ b/.github/workflows/pullpreview.yml @@ -45,7 +45,7 @@ jobs: run: | cp ./docker/pullpreview/docker-compose.yml ./docker-compose.pullpreview.yml cp ./docker/prod/Dockerfile ./Dockerfile - - uses: pullpreview/action@v5 + - uses: pullpreview/action@v6 with: # allows to ssh to the instance using our GitHub ssh key # command like: ssh ec2-user@pr-19375-trial-ip-3-127-219-135.my.preview.run From c212df5b813863663df2a8ac8493652d4fcda95e Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Mon, 2 Feb 2026 11:06:25 +0100 Subject: [PATCH 181/293] Use more visible scoping for users --- app/components/users/hover_card_component.rb | 2 +- app/controllers/placeholder_users/memberships_controller.rb | 3 ++- app/controllers/placeholder_users_controller.rb | 4 ++-- app/controllers/shares_controller.rb | 2 +- app/controllers/users/memberships_controller.rb | 3 ++- .../creation_wizard/create_artifact_work_package_service.rb | 2 +- app/views/oauth/applications/_form.html.erb | 2 +- app/views/oauth/applications/show.html.erb | 2 +- lib/api/v3/work_packages/watchers_api.rb | 4 ++-- modules/avatars/app/controllers/avatars/users_controller.rb | 2 +- modules/costs/app/controllers/costlog_controller.rb | 6 +++--- modules/reporting/app/helpers/reporting_helper.rb | 2 +- .../users/two_factor_devices_controller.rb | 3 ++- 13 files changed, 20 insertions(+), 17 deletions(-) diff --git a/app/components/users/hover_card_component.rb b/app/components/users/hover_card_component.rb index 52da4ee0ced..7b04ba87313 100644 --- a/app/components/users/hover_card_component.rb +++ b/app/components/users/hover_card_component.rb @@ -34,7 +34,7 @@ class Users::HoverCardComponent < ApplicationComponent def initialize(id:) super - @user = User.find_by(id:) + @user = User.visible.find_by(id:) end def render? diff --git a/app/controllers/placeholder_users/memberships_controller.rb b/app/controllers/placeholder_users/memberships_controller.rb index a979f2bce23..1ebce719388 100644 --- a/app/controllers/placeholder_users/memberships_controller.rb +++ b/app/controllers/placeholder_users/memberships_controller.rb @@ -30,13 +30,14 @@ class PlaceholderUsers::MembershipsController < ApplicationController include IndividualPrincipals::MembershipControllerMethods + layout "admin" before_action :authorize_global before_action :find_individual_principal def find_individual_principal - @individual_principal = PlaceholderUser.find(params[:placeholder_user_id]) + @individual_principal = PlaceholderUser.visible.find(params[:placeholder_user_id]) end def redirected_to_tab(_membership) diff --git a/app/controllers/placeholder_users_controller.rb b/app/controllers/placeholder_users_controller.rb index 77ba46d8b12..1b2caefff71 100644 --- a/app/controllers/placeholder_users_controller.rb +++ b/app/controllers/placeholder_users_controller.rb @@ -111,7 +111,7 @@ class PlaceholderUsersController < ApplicationController respond_to do |format| format.html do flash[:notice] = I18n.t(:notice_successful_update) - redirect_back(fallback_location: edit_placeholder_user_path(@placeholder_user)) + redirect_back_or_to(edit_placeholder_user_path(@placeholder_user)) end end else @@ -146,7 +146,7 @@ class PlaceholderUsersController < ApplicationController private def find_placeholder_user - @placeholder_user = PlaceholderUser.find(params[:id]) + @placeholder_user = PlaceholderUser.visible.find(params[:id]) end protected diff --git a/app/controllers/shares_controller.rb b/app/controllers/shares_controller.rb index 850dd72b3f8..51997db62b9 100644 --- a/app/controllers/shares_controller.rb +++ b/app/controllers/shares_controller.rb @@ -57,7 +57,7 @@ class SharesController < ApplicationController visible_shares_before_adding = sharing_strategy.shares.present? find_or_create_users(send_notification: send_notification?) do |member_params| - user = User.find_by(id: member_params[:user_id]) + user = User.visible.find_by(id: member_params[:user_id]) if user.present? && (user.locked? || user.deleted?) @errors.add(:base, I18n.t("sharing.warning_locked_user", user: user.name)) else diff --git a/app/controllers/users/memberships_controller.rb b/app/controllers/users/memberships_controller.rb index 5e012762f29..044717f84ed 100644 --- a/app/controllers/users/memberships_controller.rb +++ b/app/controllers/users/memberships_controller.rb @@ -30,13 +30,14 @@ class Users::MembershipsController < ApplicationController include IndividualPrincipals::MembershipControllerMethods + layout "admin" before_action :authorize_global before_action :find_individual_principal def find_individual_principal - @individual_principal = User.find(params[:user_id]) + @individual_principal = User.visible.find(params[:user_id]) end def redirected_to_tab(membership) diff --git a/app/services/projects/creation_wizard/create_artifact_work_package_service.rb b/app/services/projects/creation_wizard/create_artifact_work_package_service.rb index 2eec2841c0f..a065a9e98bc 100644 --- a/app/services/projects/creation_wizard/create_artifact_work_package_service.rb +++ b/app/services/projects/creation_wizard/create_artifact_work_package_service.rb @@ -182,7 +182,7 @@ module Projects::CreationWizard end def assignee_mention_tag - principal = Principal.find(assigned_to_id) + principal = Principal.visible.find(assigned_to_id) ApplicationController.helpers.content_tag( "mention", diff --git a/app/views/oauth/applications/_form.html.erb b/app/views/oauth/applications/_form.html.erb index e8ed22fc5fb..7d129855d93 100644 --- a/app/views/oauth/applications/_form.html.erb +++ b/app/views/oauth/applications/_form.html.erb @@ -110,7 +110,7 @@ See COPYRIGHT and LICENSE files for more details.

    <% if @application.client_credentials_user_id %>

    - <% user = User.find(@application.client_credentials_user_id) %> + <% user = User.visible.find(@application.client_credentials_user_id) %> <%= t("oauth.client_credentials_impersonation_set_to") %> <%= link_to_user user %>

    diff --git a/app/views/oauth/applications/show.html.erb b/app/views/oauth/applications/show.html.erb index abf63bb0592..cbb765714aa 100644 --- a/app/views/oauth/applications/show.html.erb +++ b/app/views/oauth/applications/show.html.erb @@ -54,7 +54,7 @@ See COPYRIGHT and LICENSE files for more details. <% component.with_attribute( key: t("oauth.client_credentials_impersonation_set_to") ) do %> - <%= link_to_user User.find_by(id: user_id) %> + <%= link_to_user User.visible.find_by(id: user_id) %>
    <%= t("oauth.client_credentials_impersonation_warning") %> <% end %> diff --git a/lib/api/v3/work_packages/watchers_api.rb b/lib/api/v3/work_packages/watchers_api.rb index 0d43ef2cc7b..bfa8f810e0a 100644 --- a/lib/api/v3/work_packages/watchers_api.rb +++ b/lib/api/v3/work_packages/watchers_api.rb @@ -77,7 +77,7 @@ module API authorize_in_project(:add_work_package_watchers, project: @work_package.project) end - user = User.find user_id + user = User.visible.find(user_id) Services::CreateWatcher.new(@work_package, user).run( success: ->(result) { status(200) unless result[:created] }, @@ -101,7 +101,7 @@ module API authorize_in_project(:delete_work_package_watchers, project: @work_package.project) end - user = User.find_by(id: params[:user_id]) + user = User.visible.find_by(id: params[:user_id]) raise ::API::Errors::NotFound unless user diff --git a/modules/avatars/app/controllers/avatars/users_controller.rb b/modules/avatars/app/controllers/avatars/users_controller.rb index 47355f881e2..ab0f79e212a 100644 --- a/modules/avatars/app/controllers/avatars/users_controller.rb +++ b/modules/avatars/app/controllers/avatars/users_controller.rb @@ -16,7 +16,7 @@ module ::Avatars end def find_user - @user = User.find(params[:id]) + @user = User.visible.find(params[:id]) end end end diff --git a/modules/costs/app/controllers/costlog_controller.rb b/modules/costs/app/controllers/costlog_controller.rb index 9ca6d5d4421..23cf25074cc 100644 --- a/modules/costs/app/controllers/costlog_controller.rb +++ b/modules/costs/app/controllers/costlog_controller.rb @@ -73,7 +73,7 @@ class CostlogController < ApplicationController elsif @cost_entry.save flash[:notice] = t(:notice_successful_update) - redirect_back fallback_location: polymorphic_path(@cost_entry.entity) + redirect_back_or_to(polymorphic_path(@cost_entry.entity)) else render action: "edit" @@ -90,7 +90,7 @@ class CostlogController < ApplicationController if request.referer.include?("cost_reports") redirect_to controller: "/cost_reports", action: :index else - redirect_back fallback_location: polymorphic_path(@cost_entry.entity) + redirect_back_or_to(polymorphic_path(@cost_entry.entity)) end end @@ -117,7 +117,7 @@ class CostlogController < ApplicationController @user = if @cost_entry.present? && @cost_entry.user_id == user_id @cost_entry.user else - User.find_by(id: user_id) + User.visible.find_by(id: user_id) end entity_id = cost_entry_params.delete(:entity_id) diff --git a/modules/reporting/app/helpers/reporting_helper.rb b/modules/reporting/app/helpers/reporting_helper.rb index 38fa948d25d..cac2ca97418 100644 --- a/modules/reporting/app/helpers/reporting_helper.rb +++ b/modules/reporting/app/helpers/reporting_helper.rb @@ -107,7 +107,7 @@ module ReportingHelper when :project_id link_to_project Project.find(value.to_i) when :user_id, :assigned_to_id, :author_id, :logged_by_id - link_to_user(User.find_by(id: value.to_i) || DeletedUser.first) + link_to_user(User.visible.find_by(id: value.to_i) || DeletedUser.first) when :tweek "#{I18n.t(:label_week)} ##{h value}" when :tmonth diff --git a/modules/two_factor_authentication/app/controllers/two_factor_authentication/users/two_factor_devices_controller.rb b/modules/two_factor_authentication/app/controllers/two_factor_authentication/users/two_factor_devices_controller.rb index 4966f0e0435..6f9cd28f2c8 100644 --- a/modules/two_factor_authentication/app/controllers/two_factor_authentication/users/two_factor_devices_controller.rb +++ b/modules/two_factor_authentication/app/controllers/two_factor_authentication/users/two_factor_devices_controller.rb @@ -14,6 +14,7 @@ module ::TwoFactorAuthentication # Password confirmation helpers and actions include PasswordConfirmation + before_action :check_password_confirmation, only: :make_default @@ -106,7 +107,7 @@ module ::TwoFactorAuthentication end def find_user - @user = User.find(params[:id]) + @user = User.visible.find(params[:id]) end def target_user From c2bc836ea13dfed3f4e9c779ae801cc01a5c26ab Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Mon, 2 Feb 2026 12:59:47 +0100 Subject: [PATCH 182/293] Consistently load work packages via visible scope --- .../work_package_hierarchy_relations_controller.rb | 4 ++-- .../work_package_relations_controller.rb | 4 ++-- .../work_package_relations_tab_controller.rb | 2 +- .../work_packages/activities_tab_controller.rb | 2 +- .../project/pdf_export/project_initiation.rb | 14 +++++++------- .../internal_mentionable_on_work_package_filter.rb | 2 +- .../work_packages/filter/relatable_filter.rb | 2 +- app/services/mcp_resources/work_package.rb | 3 +-- app/services/work_packages/update_service.rb | 10 +++++----- .../v3/work_packages/work_package_representer.rb | 2 +- lib/api/v3/work_packages/work_packages_api.rb | 2 +- .../patches/set_attributes_service_patch.rb | 3 ++- .../app/services/bim/bcf/issues/create_service.rb | 2 +- .../app/services/bim/bcf/issues/delete_service.rb | 2 +- .../costs/app/controllers/costlog_controller.rb | 8 ++++---- modules/reporting/app/helpers/reporting_helper.rb | 2 +- 16 files changed, 32 insertions(+), 32 deletions(-) diff --git a/app/controllers/work_package_hierarchy_relations_controller.rb b/app/controllers/work_package_hierarchy_relations_controller.rb index 66cf444a2b5..cda4f8e9e4d 100644 --- a/app/controllers/work_package_hierarchy_relations_controller.rb +++ b/app/controllers/work_package_hierarchy_relations_controller.rb @@ -65,7 +65,7 @@ class WorkPackageHierarchyRelationsController < ApplicationController end def destroy - related = WorkPackage.find(params[:id]) + related = WorkPackage.visible.find(params[:id]) service_result = if related.parent_id == @work_package.id set_relation(child: related, parent: nil) @@ -101,7 +101,7 @@ class WorkPackageHierarchyRelationsController < ApplicationController def related_work_package @related_work_package ||= if params[:work_package][:id].present? - WorkPackage.find(params[:work_package][:id]) + WorkPackage.visible.find(params[:work_package][:id]) else WorkPackage.new end diff --git a/app/controllers/work_package_relations_controller.rb b/app/controllers/work_package_relations_controller.rb index 181bd932cce..50c51722093 100644 --- a/app/controllers/work_package_relations_controller.rb +++ b/app/controllers/work_package_relations_controller.rb @@ -127,11 +127,11 @@ class WorkPackageRelationsController < ApplicationController end def set_work_package - @work_package = WorkPackage.find(params[:work_package_id]) + @work_package = WorkPackage.visible.find(params[:work_package_id]) end def set_relation - @relation = @work_package.relations.find(params[:id]) + @relation = @work_package.relations.visible.find(params[:id]) end def create_relation_params diff --git a/app/controllers/work_package_relations_tab_controller.rb b/app/controllers/work_package_relations_tab_controller.rb index 1c8fda279ef..6fb17c3fbfa 100644 --- a/app/controllers/work_package_relations_tab_controller.rb +++ b/app/controllers/work_package_relations_tab_controller.rb @@ -51,7 +51,7 @@ class WorkPackageRelationsTabController < ApplicationController private def set_work_package - @work_package = WorkPackage.find(params[:work_package_id]) + @work_package = WorkPackage.visible.find(params[:work_package_id]) @project = @work_package.project # required for authorization via before_action end end diff --git a/app/controllers/work_packages/activities_tab_controller.rb b/app/controllers/work_packages/activities_tab_controller.rb index c93c1c22f38..0f642a64260 100644 --- a/app/controllers/work_packages/activities_tab_controller.rb +++ b/app/controllers/work_packages/activities_tab_controller.rb @@ -227,7 +227,7 @@ class WorkPackages::ActivitiesTabController < ApplicationController end def find_work_package - @work_package = WorkPackage.find(params[:work_package_id]) + @work_package = WorkPackage.visible.find(params[:work_package_id]) rescue ActiveRecord::RecordNotFound respond_with_error(I18n.t("label_not_found")) end diff --git a/app/models/project/pdf_export/project_initiation.rb b/app/models/project/pdf_export/project_initiation.rb index 1feb1276647..4ebe6d26175 100644 --- a/app/models/project/pdf_export/project_initiation.rb +++ b/app/models/project/pdf_export/project_initiation.rb @@ -188,12 +188,12 @@ class Project::PDFExport::ProjectInitiation < Exports::Exporter .where(id: enabled_in_wizard_ids) .group_by(&:project_custom_field_section) .map do |section, custom_fields| - { - caption: section.name, - fields: custom_fields.map do |custom_field| - { key: "cf_#{custom_field.id}", caption: custom_field.name, custom_field: } - end - } + { + caption: section.name, + fields: custom_fields.map do |custom_field| + { key: "cf_#{custom_field.id}", caption: custom_field.name, custom_field: } + end + } end end @@ -284,7 +284,7 @@ class Project::PDFExport::ProjectInitiation < Exports::Exporter def project_initiation_work_package_status return nil if project.project_creation_wizard_artifact_work_package_id.blank? - work_package = WorkPackage.find_by(id: project.project_creation_wizard_artifact_work_package_id) + work_package = WorkPackage.visible.find_by(id: project.project_creation_wizard_artifact_work_package_id) work_package&.status end diff --git a/app/models/queries/principals/filters/internal_mentionable_on_work_package_filter.rb b/app/models/queries/principals/filters/internal_mentionable_on_work_package_filter.rb index e6df3a2e57c..4ed8254cd33 100644 --- a/app/models/queries/principals/filters/internal_mentionable_on_work_package_filter.rb +++ b/app/models/queries/principals/filters/internal_mentionable_on_work_package_filter.rb @@ -82,6 +82,6 @@ class Queries::Principals::Filters::InternalMentionableOnWorkPackageFilter < end def work_package - WorkPackage.find(values.first) + WorkPackage.visible.find(values.first) end end diff --git a/app/models/queries/work_packages/filter/relatable_filter.rb b/app/models/queries/work_packages/filter/relatable_filter.rb index e5cea285c9e..988cbb5e5ce 100644 --- a/app/models/queries/work_packages/filter/relatable_filter.rb +++ b/app/models/queries/work_packages/filter/relatable_filter.rb @@ -49,7 +49,7 @@ class Queries::WorkPackages::Filter::RelatableFilter < Queries::WorkPackages::Fi end def apply_to(query_scope) - query_scope.relatable(WorkPackage.find_by(id: values.first), scope_operator) + query_scope.relatable(WorkPackage.visible.find_by(id: values.first), scope_operator) end private diff --git a/app/services/mcp_resources/work_package.rb b/app/services/mcp_resources/work_package.rb index 58fe41ca792..0537e6c3a3d 100644 --- a/app/services/mcp_resources/work_package.rb +++ b/app/services/mcp_resources/work_package.rb @@ -37,9 +37,8 @@ module McpResources default_description "Access work packages of this OpenProject instance." def read(id:) - work_package = ::WorkPackage.find_by(id:) + work_package = ::WorkPackage.visible.find_by(id:) return nil if work_package.nil? - return nil unless current_user.allowed_in_work_package?(:view_work_packages, work_package) API::V3::WorkPackages::WorkPackageRepresenter.create(work_package, current_user:, embed_links: true) end diff --git a/app/services/work_packages/update_service.rb b/app/services/work_packages/update_service.rb index 36e73d6cb07..a6fd035df11 100644 --- a/app/services/work_packages/update_service.rb +++ b/app/services/work_packages/update_service.rb @@ -142,7 +142,7 @@ class WorkPackages::UpdateService < BaseServices::Update # if parent changed, the former parent needs to be rescheduled too. if parent_just_changed?(work_package) - former_parent = WorkPackage.find_by(id: work_package.parent_id_before_last_save) + former_parent = WorkPackage.visible(user).find_by(id: work_package.parent_id_before_last_save) work_packages_to_reschedule << former_parent if former_parent end @@ -165,11 +165,11 @@ class WorkPackages::UpdateService < BaseServices::Update service_calls .group_by { |sc| sc.result.id } .map do |(_, same_work_package_calls)| - same_work_package_calls.pop.tap do |master| - same_work_package_calls.each do |sc| - master.result.attributes = sc.result.changes.transform_values(&:last) + same_work_package_calls.pop.tap do |master| + same_work_package_calls.each do |sc| + master.result.attributes = sc.result.changes.transform_values(&:last) + end end - end end end end diff --git a/lib/api/v3/work_packages/work_package_representer.rb b/lib/api/v3/work_packages/work_package_representer.rb index c72086f1ca2..4a5f03e5307 100644 --- a/lib/api/v3/work_packages/work_package_representer.rb +++ b/lib/api/v3/work_packages/work_package_representer.rb @@ -596,7 +596,7 @@ module API expected_version: "3", expected_namespace: "work_packages" - WorkPackage.find_by(id:) || + WorkPackage.visible.find_by(id:) || ::WorkPackage::InexistentWorkPackage.new(id:) end diff --git a/lib/api/v3/work_packages/work_packages_api.rb b/lib/api/v3/work_packages/work_packages_api.rb index 67954b3c349..bfab66edf11 100644 --- a/lib/api/v3/work_packages/work_packages_api.rb +++ b/lib/api/v3/work_packages/work_packages_api.rb @@ -71,7 +71,7 @@ module API end after_validation do - @work_package = WorkPackage.find(declared_params[:id]) + @work_package = WorkPackage.visible.find(declared_params[:id]) authorize_in_work_package(:view_work_packages, work_package: @work_package) do raise API::Errors::NotFound.new model: :work_package diff --git a/modules/backlogs/lib/open_project/backlogs/patches/set_attributes_service_patch.rb b/modules/backlogs/lib/open_project/backlogs/patches/set_attributes_service_patch.rb index 8d52239030a..4b728019f71 100644 --- a/modules/backlogs/lib/open_project/backlogs/patches/set_attributes_service_patch.rb +++ b/modules/backlogs/lib/open_project/backlogs/patches/set_attributes_service_patch.rb @@ -66,11 +66,12 @@ module OpenProject::Backlogs::Patches::SetAttributesServicePatch def ancestor_chain(parent_id) ancestors = [] unless parent_id.nil? - real_parent = WorkPackage.find_by(id: parent_id) + real_parent = WorkPackage.visible(user).find_by(id: parent_id) # Sort immediate ancestors first ancestors = real_parent .ancestors + .visible(user) .includes(project: :enabled_modules) .order_by_ancestors("desc") .select("work_packages.*, COALESCE(max_depth.depth, 0)") diff --git a/modules/bim/app/services/bim/bcf/issues/create_service.rb b/modules/bim/app/services/bim/bcf/issues/create_service.rb index 38302bda10f..59cfcf61396 100644 --- a/modules/bim/app/services/bim/bcf/issues/create_service.rb +++ b/modules/bim/app/services/bim/bcf/issues/create_service.rb @@ -57,7 +57,7 @@ module Bim::Bcf end def use_work_package(links:, params:) - work_package = WorkPackage.find_by(id: work_package_id_from_links(links)) + work_package = WorkPackage.visible(user).find_by(id: work_package_id_from_links(links)) return work_package_not_found_result if work_package.nil? ::WorkPackages::UpdateService diff --git a/modules/bim/app/services/bim/bcf/issues/delete_service.rb b/modules/bim/app/services/bim/bcf/issues/delete_service.rb index a52c41ac895..63f6b92169a 100644 --- a/modules/bim/app/services/bim/bcf/issues/delete_service.rb +++ b/modules/bim/app/services/bim/bcf/issues/delete_service.rb @@ -42,7 +42,7 @@ module Bim::Bcf end def work_package_delete_call(params) - associated_wp = WorkPackage.find(model.work_package_id) + associated_wp = WorkPackage.visible(user).find(model.work_package_id) # Load the project association as AR fails do do so once the work package # is destroyed. model.project diff --git a/modules/costs/app/controllers/costlog_controller.rb b/modules/costs/app/controllers/costlog_controller.rb index 23cf25074cc..79904e0f8e4 100644 --- a/modules/costs/app/controllers/costlog_controller.rb +++ b/modules/costs/app/controllers/costlog_controller.rb @@ -99,13 +99,13 @@ class CostlogController < ApplicationController def find_project # copied from timelog_controller.rb if params[:id] - @cost_entry = CostEntry.find(params[:id]) + @cost_entry = CostEntry.visible.find(params[:id]) @project = @cost_entry.project elsif params[:work_package_id] - @work_package = WorkPackage.find(params[:work_package_id]) + @work_package = WorkPackage.visible.find(params[:work_package_id]) @project = @work_package.project elsif params[:project_id] - @project = Project.find(params[:project_id]) + @project = Project.visible.find(params[:project_id]) else render_404 false @@ -125,7 +125,7 @@ class CostlogController < ApplicationController @work_package = if @cost_entry.present? && @cost_entry.entity_type == "WorkPackage" && @cost_entry.entity_id == entity_id @cost_entry.entity elsif entity_type == "WorkPackage" - WorkPackage.find_by(id: entity_id) + WorkPackage.visible.find_by(id: entity_id) end cost_type_id = cost_entry_params.delete(:cost_type_id) diff --git a/modules/reporting/app/helpers/reporting_helper.rb b/modules/reporting/app/helpers/reporting_helper.rb index cac2ca97418..01d4ad65965 100644 --- a/modules/reporting/app/helpers/reporting_helper.rb +++ b/modules/reporting/app/helpers/reporting_helper.rb @@ -119,7 +119,7 @@ module ReportingHelper when :budget_id budget_link value when :work_package_id - link_to_work_package(WorkPackage.find(value.to_i)) + link_to_work_package(WorkPackage.visible.find(value.to_i)) when :entity_gid allowed_types = (TimeEntry::ALLOWED_ENTITY_TYPES | CostEntry::ALLOWED_ENTITY_TYPES).map(&:safe_constantize) entity = begin From 2b7a3c819cc6dd6090365cd4ea27ffb7682a9022 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Mon, 2 Feb 2026 13:03:58 +0100 Subject: [PATCH 183/293] Find more resources via visible scope --- app/controllers/groups_controller.rb | 2 +- modules/costs/lib/api/v3/cost_entries/cost_entries_api.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 09616c8103d..8ba62ac7f71 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -150,7 +150,7 @@ class GroupsController < ApplicationController protected def find_group - @group = Group.find(params[:id]) + @group = Group.visible.find(params[:id]) end def group_members diff --git a/modules/costs/lib/api/v3/cost_entries/cost_entries_api.rb b/modules/costs/lib/api/v3/cost_entries/cost_entries_api.rb index 4f0a9dc912a..c8b03f2cf6c 100644 --- a/modules/costs/lib/api/v3/cost_entries/cost_entries_api.rb +++ b/modules/costs/lib/api/v3/cost_entries/cost_entries_api.rb @@ -35,7 +35,7 @@ module API resources :cost_entries do route_param :id, type: Integer, desc: "Cost entry ID" do after_validation do - @cost_entry = CostEntry.find(params[:id]) + @cost_entry = CostEntry.visible.find(params[:id]) authorize_in_project(:view_cost_entries, project: @cost_entry.project) do if current_user == @cost_entry.user From b4dcdf467e87cafa2b0c4f3447b71665f4d371bc Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Mon, 2 Feb 2026 13:36:51 +0100 Subject: [PATCH 184/293] Remove generic find methods in controllers --- .../custom_field_projects_controller.rb | 11 ++- .../hierarchy/items_base_controller.rb | 8 +-- app/controllers/application_controller.rb | 68 +------------------ app/controllers/categories_controller.rb | 15 ++-- app/controllers/custom_actions_controller.rb | 7 +- .../ldap_auth_sources_controller.rb | 8 ++- app/controllers/members_controller.rb | 8 ++- app/controllers/messages_controller.rb | 9 ++- app/controllers/news/comments_controller.rb | 17 ++++- app/controllers/versions_controller.rb | 11 +-- app/models/members/scopes/visible.rb | 2 +- .../patches/versions_controller_patch.rb | 15 +--- .../app/controllers/documents_controller.rb | 8 ++- .../work_package_costlog_controller.rb | 5 +- .../admin/access_management_controller.rb | 8 +-- ...ally_managed_project_folders_controller.rb | 26 +++---- .../admin/health_status_controller.rb | 9 +-- .../admin/project_storages_controller.rb | 17 +++-- .../storages/project_storages_controller.rb | 37 +++++----- .../storages/admin/storages_controller.rb | 9 +-- .../project_storage_members_controller.rb | 10 ++- .../storages/project_storages_controller.rb | 11 +-- 22 files changed, 126 insertions(+), 193 deletions(-) diff --git a/app/controllers/admin/custom_fields/custom_field_projects_controller.rb b/app/controllers/admin/custom_fields/custom_field_projects_controller.rb index 933b5b8a35d..86d34737613 100644 --- a/app/controllers/admin/custom_fields/custom_field_projects_controller.rb +++ b/app/controllers/admin/custom_fields/custom_field_projects_controller.rb @@ -34,10 +34,8 @@ class Admin::CustomFields::CustomFieldProjectsController < ApplicationController layout "admin" - model_object CustomField - before_action :require_admin - before_action :find_model_object + before_action :find_custom_field, except: %i[index new create] before_action :available_custom_fields_projects_query, only: %i[index destroy] before_action :initialize_custom_field_project, only: :new @@ -99,14 +97,13 @@ class Admin::CustomFields::CustomFieldProjectsController < ApplicationController ) end - def find_model_object(object_id = :custom_field_id) - super - @custom_field = @object + def find_custom_field + @custom_field = CustomField.find(params[:custom_field_id]) end def find_projects_to_activate_for_custom_field if (project_ids = params.to_unsafe_h[:custom_fields_project][:project_ids]).present? - @projects = Project.find(project_ids) + @projects = Project.visible.find(project_ids) else initialize_custom_field_project @custom_field_project.errors.add(:project_ids, :blank) diff --git a/app/controllers/admin/custom_fields/hierarchy/items_base_controller.rb b/app/controllers/admin/custom_fields/hierarchy/items_base_controller.rb index 56a84fa6246..41cb30747d5 100644 --- a/app/controllers/admin/custom_fields/hierarchy/items_base_controller.rb +++ b/app/controllers/admin/custom_fields/hierarchy/items_base_controller.rb @@ -36,9 +36,8 @@ module Admin include Dry::Monads[:result] layout :admin_or_frame_layout - model_object CustomField - before_action :require_admin, :find_model_object, :find_active_item + before_action :require_admin, :find_custom_field, :find_active_item # See https://github.com/hotwired/turbo-rails?tab=readme-ov-file#a-note-on-custom-layouts def admin_or_frame_layout @@ -225,11 +224,6 @@ module Admin end end - def find_model_object - @object = find_custom_field - @custom_field = @object - end - def find_custom_field raise NotImplementedError, "SubclassResponsibility" end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index cf4918694a5..e77963a5411 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -34,7 +34,6 @@ require "cgi" require "doorkeeper/dashboard_helper" class ApplicationController < ActionController::Base - class_attribute :_model_object class_attribute :_model_scope class_attribute :accept_key_auth_actions @@ -246,18 +245,18 @@ class ApplicationController < ActionController::Base # Find project of id params[:id] # Note: find() is Project.friendly.find() def find_project - @project = Project.find(params[:id]) + @project = Project.visible.find(params[:id]) end # Find project of id params[:project_id] # Note: find() is Project.friendly.find() def find_project_by_project_id - @project = Project.find(params[:project_id]) + @project = Project.visible.find(params[:project_id]) end # Find project by project_id if given def find_optional_project - @project = Project.find(params[:project_id]) if params[:project_id].present? + @project = Project.visible.find(params[:project_id]) if params[:project_id].present? rescue ActiveRecord::RecordNotFound render_404 end @@ -269,67 +268,6 @@ class ApplicationController < ActionController::Base @project = @object.project end - def find_model_object(object_id = :id) - model = self.class._model_object - if model - @object = model.find(params[object_id]) - instance_variable_set(:"@#{controller_name.singularize}", @object) if @object - end - end - - def find_model_object_and_project(object_id = :id) - if params[object_id] - model_object = self.class._model_object - instance = model_object.find(params[object_id]) - @project = instance.project - instance_variable_set(:"@#{model_object.to_s.underscore}", instance) - else - @project = Project.find(params[:project_id]) - end - end - - # TODO: this method is right now only suited for controllers of objects that somehow have an association to Project - def find_object_and_scope - model_object = self.class._model_object.find(params[:id]) if params[:id].present? - - associations = self.class._model_scope + [Project] - - associated = find_belongs_to_chained_objects(associations, model_object) - - associated.each do |a| - instance_variable_set("@" + a.class.to_s.downcase, a) - end - end - - # this method finds all records that are specified in the associations param - # after the first object is found it traverses the belongs_to chain of that first object - # if a start_object is provided it is taken as the starting point of the traversal - # e.g associations [Message, Board, Project] finds Message by find(:message_id) - # then message.forum and board.project - def find_belongs_to_chained_objects(associations, start_object = nil) - associations.inject([start_object].compact) do |instances, association| - scope_name, scope_association = if association.is_a?(Hash) - [association.keys.first.to_s.downcase, association.values.first] - else - [association.to_s.downcase, association.to_s.downcase] - end - - # TODO: Remove this hidden dependency on params - instances << ( - if instances.last.nil? - scope_name.camelize.constantize.find(params[:"#{scope_name}_id"]) - else - instances.last.send(scope_association.to_sym) - end) - instances - end - end - - def self.model_object(model, options = {}) - self._model_object = model - self._model_scope = Array(options[:scope]) if options[:scope] - end - # Filter for bulk work package operations def find_work_packages @work_packages = WorkPackage.includes(:project) diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb index c5f5b4f6c4f..c26c6b87230 100644 --- a/app/controllers/categories_controller.rb +++ b/app/controllers/categories_controller.rb @@ -30,9 +30,8 @@ class CategoriesController < ApplicationController menu_item :settings_categories - model_object Category - before_action :find_model_object, except: %i[new create] - before_action :find_project_from_association, except: %i[new create] + + before_action :find_category_and_project, except: %i[new create] before_action :find_project, only: %i[new create] before_action :authorize @@ -81,14 +80,12 @@ class CategoriesController < ApplicationController private - # Wrap ApplicationController's find_model_object method to set - # @category instead of just @category - def find_model_object - super - @category = @object + def find_category_and_project + @category = Category.find(params[:id]) + @project = @category.project end def find_project - @project = Project.find(params[:project_id]) + @project = Project.visible.find(params[:project_id]) end end diff --git a/app/controllers/custom_actions_controller.rb b/app/controllers/custom_actions_controller.rb index bfeb80dd4b2..62c766a5896 100644 --- a/app/controllers/custom_actions_controller.rb +++ b/app/controllers/custom_actions_controller.rb @@ -35,8 +35,7 @@ class CustomActionsController < ApplicationController redirect_to action: :index end - self._model_object = CustomAction - before_action :find_model_object, only: %i(edit update destroy) + before_action :find_custom_action, only: %i(edit update destroy) before_action :pad_params, only: %i(create update) layout "admin" @@ -73,6 +72,10 @@ class CustomActionsController < ApplicationController private + def find_custom_action + @custom_action = CustomAction.find(params[:id]) + end + def index_or_render(render_action) ->(call) { call.on_success do diff --git a/app/controllers/ldap_auth_sources_controller.rb b/app/controllers/ldap_auth_sources_controller.rb index f0e8a072c79..12ac4573eab 100644 --- a/app/controllers/ldap_auth_sources_controller.rb +++ b/app/controllers/ldap_auth_sources_controller.rb @@ -31,13 +31,13 @@ class LdapAuthSourcesController < ApplicationController menu_item :ldap_authentication include PaginationHelper + layout "admin" before_action :require_admin before_action :block_if_password_login_disabled - self._model_object = LdapAuthSource - before_action :find_model_object, only: %i(edit update destroy) + before_action :find_ldap_auth_source, only: %i(edit update destroy) before_action :prevent_editing_when_seeded, only: %i(update) def index @@ -101,6 +101,10 @@ class LdapAuthSourcesController < ApplicationController protected + def find_ldap_auth_source + @ldap_auth_source = LdapAuthSource.find(params[:id]) + end + def prevent_editing_when_seeded if @ldap_auth_source.seeded_from_env? flash[:warning] = I18n.t(:label_seeded_from_env_warning) diff --git a/app/controllers/members_controller.rb b/app/controllers/members_controller.rb index b6c58613770..8c3999f9e8f 100644 --- a/app/controllers/members_controller.rb +++ b/app/controllers/members_controller.rb @@ -31,8 +31,7 @@ class MembersController < ApplicationController include MemberHelper - model_object Member - before_action :find_model_object_and_project, except: %i[autocomplete_for_member destroy_by_principal] + before_action :find_member, except: %i[autocomplete_for_member destroy_by_principal] before_action :find_project_by_project_id, only: %i[autocomplete_for_member destroy_by_principal] before_action :authorize @@ -118,6 +117,10 @@ class MembersController < ApplicationController private + def find_member + @member = @project.members.visible.find(params[:id]) + end + def authorize_for(controller, action) current_user.allowed_in_project?({ controller:, action: }, @project) end @@ -205,6 +208,7 @@ class MembersController < ApplicationController def possible_members(criteria, limit, type: nil) Principal + .visible .possible_member(@project, type:) .like(criteria, email: user_allowed_to_view_emails?) .limit(limit) diff --git a/app/controllers/messages_controller.rb b/app/controllers/messages_controller.rb index c016d914e06..7ce3158c4f3 100644 --- a/app/controllers/messages_controller.rb +++ b/app/controllers/messages_controller.rb @@ -31,8 +31,7 @@ class MessagesController < ApplicationController menu_item :forums default_search_scope :messages - model_object Message, scope: Forum - before_action :find_object_and_scope + before_action :find_project_forum_and_message before_action :authorize, except: %i[edit update destroy] # Checked inside the method. no_authorization_required! :edit, :update, :destroy @@ -157,6 +156,12 @@ class MessagesController < ApplicationController private + def find_project_forum_and_message + @message = Message.visible(find_params[:id]) + @forum = @message.forum + @project = @forum.project + end + def update_message(message) Messages::UpdateService .new(user: current_user, diff --git a/app/controllers/news/comments_controller.rb b/app/controllers/news/comments_controller.rb index ca5cf324793..76d090c430a 100644 --- a/app/controllers/news/comments_controller.rb +++ b/app/controllers/news/comments_controller.rb @@ -30,8 +30,8 @@ class News::CommentsController < ApplicationController default_search_scope :news - model_object Comment, scope: [News => :commented] - before_action :find_object_and_scope + before_action :find_project_news_and_comment, only: %i[destroy] + before_action :find_news, only: %i[create] before_action :authorize def create @@ -48,4 +48,17 @@ class News::CommentsController < ApplicationController @comment.destroy redirect_to news_path(@news), status: :see_other end + + private + + def find_project_news_and_comment + @comment = Comment.find(params[:id]) + @news = News.visible.find(@comment.commented_id) + @project = @news.project + end + + def find_news + @news = News.visible.find(params[:news_id]) + @project = @news.project + end end diff --git a/app/controllers/versions_controller.rb b/app/controllers/versions_controller.rb index d6b0d31be0b..e2d1ff3d116 100644 --- a/app/controllers/versions_controller.rb +++ b/app/controllers/versions_controller.rb @@ -32,9 +32,7 @@ class VersionsController < ApplicationController menu_item :roadmap, only: %i(index show) menu_item :settings_versions - model_object Version - before_action :find_model_object, except: %i[index new create close_completed] - before_action :find_project_from_association, except: %i[index new create close_completed] + before_action :find_version, except: %i[index new create close_completed] before_action :find_project, only: %i[index new create close_completed] before_action :authorize @@ -114,6 +112,11 @@ class VersionsController < ApplicationController private + def find_version + @version = Version.visible.find(params[:id]) + @project = @version.project + end + def archived_project_mesage if current_user.admin? ApplicationController.helpers.sanitize( @@ -135,7 +138,7 @@ class VersionsController < ApplicationController end def find_project - @project = Project.find(params[:project_id]) + @project = Project.visible.find(params[:project_id]) end def retrieve_selected_type_ids(selectable_types, default_types = nil) diff --git a/app/models/members/scopes/visible.rb b/app/models/members/scopes/visible.rb index 1c6e71f7ed1..8fe3adafe2e 100644 --- a/app/models/members/scopes/visible.rb +++ b/app/models/members/scopes/visible.rb @@ -34,7 +34,7 @@ module Members::Scopes class_methods do # Find all members visible to the inquiring user - def visible(user) + def visible(user = User.current) if user.admin? visible_for_admins else diff --git a/modules/backlogs/lib/open_project/backlogs/patches/versions_controller_patch.rb b/modules/backlogs/lib/open_project/backlogs/patches/versions_controller_patch.rb index aa28eba00b1..b3b741750d3 100644 --- a/modules/backlogs/lib/open_project/backlogs/patches/versions_controller_patch.rb +++ b/modules/backlogs/lib/open_project/backlogs/patches/versions_controller_patch.rb @@ -30,12 +30,8 @@ module OpenProject::Backlogs::Patches::VersionsControllerPatch def self.included(base) base.class_eval do include VersionSettingsHelper - helper :version_settings - # Find project explicitly on update and edit - skip_before_action :find_project_from_association, only: %i[edit update] - skip_before_action :find_model_object, only: %i[edit update] - prepend_before_action :find_project_and_version, only: %i[edit update] + helper :version_settings before_action :add_project_to_version_settings_attributes, only: %i[update create] @@ -56,15 +52,6 @@ module OpenProject::Backlogs::Patches::VersionsControllerPatch end end - def find_project_and_version - find_model_object - if params[:project_id] - find_project - else - find_project_from_association - end - end - # This forces the current project for the nested version settings in order # to prevent it from being set through firebug etc. #mass_assignment def add_project_to_version_settings_attributes diff --git a/modules/documents/app/controllers/documents_controller.rb b/modules/documents/app/controllers/documents_controller.rb index be69bc1595e..cac49b807c3 100644 --- a/modules/documents/app/controllers/documents_controller.rb +++ b/modules/documents/app/controllers/documents_controller.rb @@ -35,11 +35,8 @@ class DocumentsController < ApplicationController include OpTurbo::ComponentStream default_search_scope :documents - model_object Document before_action :find_project_by_project_id, only: %i[index search new create] - before_action :find_model_object, except: %i[index search new create] - before_action :find_project_from_association, except: %i[index search new create] before_action :authorize def index @@ -189,6 +186,11 @@ class DocumentsController < ApplicationController private + def find_document + @document = Document.visible.find(params[:id]) + @project = @document.project + end + def document_params params.fetch(:document, {}).permit("type_id", "title", "description", "content_binary", "kind") end diff --git a/modules/reporting/app/controllers/work_package_costlog_controller.rb b/modules/reporting/app/controllers/work_package_costlog_controller.rb index 26b14f12946..954a5bf322f 100644 --- a/modules/reporting/app/controllers/work_package_costlog_controller.rb +++ b/modules/reporting/app/controllers/work_package_costlog_controller.rb @@ -27,9 +27,8 @@ #++ class WorkPackageCostlogController < ApplicationController - model_object WorkPackage - menu_item :work_packages + before_action :find_objects before_action :authorize before_action :redirect_when_outside_project @@ -74,7 +73,7 @@ class WorkPackageCostlogController < ApplicationController # 1. Work package from :work_package_id and its #project # 2. Cost Type from param def find_objects - find_model_object_and_project :work_package_id + @work_package = WorkPackage.visible.find_by(id: params[:work_package_id]) if params[:cost_type_id].present? @cost_type = CostType.find(params[:cost_type_id]) diff --git a/modules/storages/app/controllers/storages/admin/access_management_controller.rb b/modules/storages/app/controllers/storages/admin/access_management_controller.rb index 2de3ddb4e03..9f750ba0472 100644 --- a/modules/storages/app/controllers/storages/admin/access_management_controller.rb +++ b/modules/storages/app/controllers/storages/admin/access_management_controller.rb @@ -37,8 +37,7 @@ class Storages::Admin::AccessManagementController < ApplicationController before_action :require_admin - model_object Storages::Storage - before_action :find_model_object, only: %i[new create edit update] + before_action :find_storage, only: %i[new create edit update] # menu_item is defined in the Redmine::MenuManager::MenuController # module, included from ApplicationController. @@ -92,9 +91,8 @@ class Storages::Admin::AccessManagementController < ApplicationController private - def find_model_object(object_id = :storage_id) - super - @storage = @object + def find_storage + @storage = Storages::Storage.find(params[:storage_id]) end def call_update_service diff --git a/modules/storages/app/controllers/storages/admin/automatically_managed_project_folders_controller.rb b/modules/storages/app/controllers/storages/admin/automatically_managed_project_folders_controller.rb index 6bd52fb31ee..650be746af2 100644 --- a/modules/storages/app/controllers/storages/admin/automatically_managed_project_folders_controller.rb +++ b/modules/storages/app/controllers/storages/admin/automatically_managed_project_folders_controller.rb @@ -41,9 +41,7 @@ class Storages::Admin::AutomaticallyManagedProjectFoldersController < Applicatio # and set the @ variable to the object referenced in the URL. before_action :require_admin - # specify which model #find_model_object should look up - model_object Storages::NextcloudStorage - before_action :find_model_object, only: %i[new create edit update] + before_action :find_nextcloud_storage, only: %i[new create edit update] # menu_item is defined in the Redmine::MenuManager::MenuController # module, included from ApplicationController. @@ -69,6 +67,13 @@ class Storages::Admin::AutomaticallyManagedProjectFoldersController < Applicatio respond_with_ampf_form_turbo_stream_or_edit_html end + # Renders an edit page (allowing the user to change automatically_managed bool and password). + # Used by: The StoragesController#edit, when user wants to update application credentials. + # Called by: Global app/config/routes.rb to serve Web page + def edit + respond_with_ampf_form_turbo_stream_or_edit_html + end + def create service_result = call_update_service @@ -83,13 +88,6 @@ class Storages::Admin::AutomaticallyManagedProjectFoldersController < Applicatio end end - # Renders an edit page (allowing the user to change automatically_managed bool and password). - # Used by: The StoragesController#edit, when user wants to update application credentials. - # Called by: Global app/config/routes.rb to serve Web page - def edit - respond_with_ampf_form_turbo_stream_or_edit_html - end - # Update is similar to create above # See also: create above # Called by: Global app/config/routes.rb to serve Web page @@ -119,12 +117,8 @@ class Storages::Admin::AutomaticallyManagedProjectFoldersController < Applicatio end end - # Override default url param `:id` to `:storage` controller is a nested storage resource - # GET /admin/settings/storages/:storage_id/automatically_managed_project_folders/new - # POST /admin/settings/storages/:storage_id/automatically_managed_project_folders - def find_model_object(object_id = :storage_id) - super - @storage = @object + def find_nextcloud_storage + @storage = Storages::Storage.find(params[:storage_id]) end def call_update_service diff --git a/modules/storages/app/controllers/storages/admin/health_status_controller.rb b/modules/storages/app/controllers/storages/admin/health_status_controller.rb index 688f811fa69..43b2f34c8e1 100644 --- a/modules/storages/app/controllers/storages/admin/health_status_controller.rb +++ b/modules/storages/app/controllers/storages/admin/health_status_controller.rb @@ -35,10 +35,8 @@ module Storages layout :admin_or_frame_layout - model_object Storage - before_action :require_admin - before_action :find_model_object + before_action :find_storage def admin_or_frame_layout return "turbo_rails/frame" if turbo_frame_request? @@ -83,9 +81,8 @@ module Storages }.merge(@report.to_h).to_yaml(stringify_names: true) end - def find_model_object(object_id = :storage_id) - super - @storage = @object + def find_storage + @storage = Storages::Storage.find(params[:storage_id]) end def create_and_cache_report diff --git a/modules/storages/app/controllers/storages/admin/project_storages_controller.rb b/modules/storages/app/controllers/storages/admin/project_storages_controller.rb index e26bacbcad7..9b8751ac4e8 100644 --- a/modules/storages/app/controllers/storages/admin/project_storages_controller.rb +++ b/modules/storages/app/controllers/storages/admin/project_storages_controller.rb @@ -32,9 +32,7 @@ class Storages::Admin::ProjectStoragesController < Projects::SettingsController include Storages::OAuthAccessGrantable include OpTurbo::ComponentStream - model_object Storages::ProjectStorage - - before_action :find_model_object, only: %i[oauth_access_grant edit update destroy destroy_info] + before_action :find_project_storage, only: %i[oauth_access_grant edit update destroy destroy_info] menu_item :settings_project_storages def external_file_storages @@ -61,7 +59,6 @@ class Storages::Admin::ProjectStoragesController < Projects::SettingsController end def edit - @project_storage = @object @project_storage.project_folder_mode = project_folder_mode_from_params if project_folder_mode_from_params.present? @last_project_folders = Storages::LastProjectFolder @@ -88,7 +85,6 @@ class Storages::Admin::ProjectStoragesController < Projects::SettingsController end def oauth_access_grant - @project_storage = @object storage = @project_storage.storage auth_state = ::Storages::Adapters::Authentication.authorization_state(storage:, user: current_user) @@ -105,7 +101,7 @@ class Storages::Admin::ProjectStoragesController < Projects::SettingsController def update service_result = ::Storages::ProjectStorages::UpdateService - .new(user: current_user, model: @object) + .new(user: current_user, model: @project_storage) .call(permitted_storage_settings_params) if service_result.success? @@ -113,14 +109,13 @@ class Storages::Admin::ProjectStoragesController < Projects::SettingsController flash[:notice] = I18n.t(:notice_successful_update) redirect_to_project_storages_path_with_oauth_access_grant_confirmation(@project_storage.storage) else - @project_storage = @object render "/storages/project_settings/edit" end end def destroy Storages::ProjectStorages::DeleteService - .new(user: current_user, model: @object) + .new(user: current_user, model: @project_storage) .call .on_failure { |service_result| flash[:error] = service_result.errors.full_messages } @@ -129,13 +124,17 @@ class Storages::Admin::ProjectStoragesController < Projects::SettingsController def destroy_info respond_with_dialog Storages::ProjectStorages::DestroyConfirmationDialogComponent.new( - project_storage: @object, + project_storage: @project_storage, target: :project ) end private + def find_project_storage + @project_storage = Storages::ProjectStorage.find(params[:id]) + end + def permitted_storage_settings_params params .require(:storages_project_storage) diff --git a/modules/storages/app/controllers/storages/admin/storages/project_storages_controller.rb b/modules/storages/app/controllers/storages/admin/storages/project_storages_controller.rb index c3636b81cd2..a07d3bac3a5 100644 --- a/modules/storages/app/controllers/storages/admin/storages/project_storages_controller.rb +++ b/modules/storages/app/controllers/storages/admin/storages/project_storages_controller.rb @@ -35,10 +35,8 @@ class Storages::Admin::Storages::ProjectStoragesController < ApplicationControll layout "admin" - model_object Storages::Storage - before_action :require_admin - before_action :find_model_object + before_action :find_storage before_action :load_project_storage, only: %i(edit update destroy destroy_confirmation_dialog) before_action :storage_projects_query, only: :index @@ -69,6 +67,17 @@ class Storages::Admin::Storages::ProjectStoragesController < ApplicationControll ) end + def edit + last_project_folders = Storages::LastProjectFolder + .where(project_storage: @project_storage) + .pluck(:mode, :origin_folder_id) + .to_h + + respond_with_dialog Storages::Admin::Storages::ProjectsStorageModalComponent.new( + project_storage: @project_storage, last_project_folders: + ) + end + def create # rubocop:disable Metrics/AbcSize create_service = ::Storages::ProjectStorages::BulkCreateService .new(user: current_user, projects: @projects, storage: @storage, @@ -87,17 +96,6 @@ class Storages::Admin::Storages::ProjectStoragesController < ApplicationControll respond_with_turbo_streams(status: create_service.success? ? :ok : :unprocessable_entity) end - def edit - last_project_folders = Storages::LastProjectFolder - .where(project_storage: @project_storage) - .pluck(:mode, :origin_folder_id) - .to_h - - respond_with_dialog Storages::Admin::Storages::ProjectsStorageModalComponent.new( - project_storage: @project_storage, last_project_folders: - ) - end - def update update_service = ::Storages::ProjectStorages::UpdateService .new(user: current_user, model: @project_storage) @@ -143,6 +141,10 @@ class Storages::Admin::Storages::ProjectStoragesController < ApplicationControll private + def load_storage + @storage = Storages::Storage.visible.find(params[:storage_id]) + end + def load_project_storage @project_storage = Storages::ProjectStorage.find(params[:id]) rescue ActiveRecord::RecordNotFound @@ -152,14 +154,9 @@ class Storages::Admin::Storages::ProjectStoragesController < ApplicationControll respond_with_turbo_streams end - def find_model_object(object_id = :storage_id) - super - @storage = @object - end - def find_projects_to_activate_for_storage if (project_ids = params.to_unsafe_h[:storages_project_storage][:project_ids]).present? - @projects = Project.find(project_ids) + @projects = Project.visible.find(project_ids) else initialize_project_storage @project_storage.errors.add(:project_ids, :blank) diff --git a/modules/storages/app/controllers/storages/admin/storages_controller.rb b/modules/storages/app/controllers/storages/admin/storages_controller.rb index bb9de9f0989..3d6fdd4ac99 100644 --- a/modules/storages/app/controllers/storages/admin/storages_controller.rb +++ b/modules/storages/app/controllers/storages/admin/storages_controller.rb @@ -41,13 +41,10 @@ module Storages # See https://guides.rubyonrails.org/layouts_and_rendering.html for reference on layout layout "admin" - # specify which model #find_model_object should look up - model_object Storage - # Before executing any action below: Make sure the current user is an admin # and set the @ variable to the object referenced in the URL. before_action :require_admin - before_action :find_model_object, + before_action :find_storage, only: %i[show_oauth_application destroy edit edit_host edit_storage_audience confirm_destroy update change_health_notifications_enabled replace_oauth_application] before_action :ensure_valid_wizard_parameters, only: [:new] @@ -216,6 +213,10 @@ module Storages private + def find_storage + @storage = Storages::Storage.visible.find(params[:id]) + end + def prepare_storage_for_access_management_form return unless @storage.automatic_management_unspecified? diff --git a/modules/storages/app/controllers/storages/project_settings/project_storage_members_controller.rb b/modules/storages/app/controllers/storages/project_settings/project_storage_members_controller.rb index 5b20e7b368c..52bfc4f111c 100644 --- a/modules/storages/app/controllers/storages/project_settings/project_storage_members_controller.rb +++ b/modules/storages/app/controllers/storages/project_settings/project_storage_members_controller.rb @@ -36,12 +36,11 @@ class Storages::ProjectSettings::ProjectStorageMembersController < Projects::Set menu_item :settings_project_storages - before_action :find_model_object, only: %i[index] - - model_object Storages::ProjectStorage + before_action :find_project_storage, only: %i[index] def index @project_users = Member + .visible .of_project(@project) .joins(:principal) .preload(roles: :role_permissions, principal: :remote_identities) @@ -53,9 +52,8 @@ class Storages::ProjectSettings::ProjectStorageMembersController < Projects::Set private - def find_model_object(object_id = :project_storage_id) - super - @project_storage = @object + def find_project_storage + @project_storage = Storages::ProjectStorage.find(params[:project_storage_id]) @storage = @project_storage.storage end end diff --git a/modules/storages/app/controllers/storages/project_storages_controller.rb b/modules/storages/app/controllers/storages/project_storages_controller.rb index 7c8fe12f364..686faec4ef6 100644 --- a/modules/storages/app/controllers/storages/project_storages_controller.rb +++ b/modules/storages/app/controllers/storages/project_storages_controller.rb @@ -32,10 +32,9 @@ class Storages::ProjectStoragesController < ApplicationController using Storages::Peripherals::ServiceResultRefinements menu_item :overview - model_object Storages::ProjectStorage before_action :require_login - before_action :find_model_object + before_action :find_project_stroage before_action :find_project_by_project_id before_action :render_403, unless: -> { User.current.allowed_in_project?(:view_file_links, @project) } no_authorization_required! :open @@ -53,6 +52,10 @@ class Storages::ProjectStoragesController < ApplicationController private + def find_project_stroage + @project_storage = Storages::ProjectStorage.find(params[:id]) + end + def ensure_remote_identity case Storages::Adapters::Authentication.authorization_state(storage:, user: current_user) when :not_connected @@ -114,7 +117,7 @@ class Storages::ProjectStoragesController < ApplicationController end def storage - @object.storage + @project_storage.storage end def project_storage_scope @@ -129,6 +132,6 @@ class Storages::ProjectStoragesController < ApplicationController def show_error(message) flash[:error] = Array(message) + [I18n.t("project_storages.open.contact_admin")] - redirect_back(fallback_location: project_path(id: @project_storage.project_id)) + redirect_back_or_to(project_path(id: @project_storage.project_id)) end end From 574402caa30698594f2cda4cb32adac61127c01f Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Mon, 2 Feb 2026 13:54:14 +0100 Subject: [PATCH 185/293] Remove shallow routing for members --- app/controllers/members_controller.rb | 6 +++--- .../work_package_hierarchy_relations_controller.rb | 2 +- app/views/members/menus/_menu.html.erb | 2 +- config/routes.rb | 7 ++----- spec/controllers/members_controller_spec.rb | 3 ++- 5 files changed, 9 insertions(+), 11 deletions(-) diff --git a/app/controllers/members_controller.rb b/app/controllers/members_controller.rb index 8c3999f9e8f..158a58d407f 100644 --- a/app/controllers/members_controller.rb +++ b/app/controllers/members_controller.rb @@ -31,8 +31,8 @@ class MembersController < ApplicationController include MemberHelper - before_action :find_member, except: %i[autocomplete_for_member destroy_by_principal] - before_action :find_project_by_project_id, only: %i[autocomplete_for_member destroy_by_principal] + before_action :find_project_by_project_id + before_action :find_member, except: %i[create autocomplete_for_member destroy_by_principal] before_action :authorize def index @@ -85,7 +85,7 @@ class MembersController < ApplicationController end def destroy_by_principal - principal = Principal.find(params[:principal_id]) + principal = Principal.visible.find(params[:principal_id]) service_call = Members::DeleteByPrincipalService .new(user: current_user, project: @project, principal:) diff --git a/app/controllers/work_package_hierarchy_relations_controller.rb b/app/controllers/work_package_hierarchy_relations_controller.rb index cda4f8e9e4d..ac48c957ec0 100644 --- a/app/controllers/work_package_hierarchy_relations_controller.rb +++ b/app/controllers/work_package_hierarchy_relations_controller.rb @@ -139,7 +139,7 @@ class WorkPackageHierarchyRelationsController < ApplicationController end def set_work_package - @work_package = WorkPackage.find(params[:work_package_id]) + @work_package = WorkPackage.visible.find(params[:work_package_id]) @project = @work_package.project end diff --git a/app/views/members/menus/_menu.html.erb b/app/views/members/menus/_menu.html.erb index 7d0a2053561..f6c51bc8b56 100644 --- a/app/views/members/menus/_menu.html.erb +++ b/app/views/members/menus/_menu.html.erb @@ -1,5 +1,5 @@ <%= turbo_frame_tag "members_menu", - src: project_members_menu_path(@project, params.permit(*Members::UserFilterComponent.filter_param_keys)), + src: menu_project_members_path(@project, params.permit(*Members::UserFilterComponent.filter_param_keys)), target: "_top", data: { turbo: false }, loading: :lazy %> diff --git a/config/routes.rb b/config/routes.rb index 9d6891ed2ad..2c10c1d9c59 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -465,18 +465,15 @@ Rails.application.routes.draw do resources :categories, except: %i[index show], shallow: true - resources :members, only: %i[index create update], shallow: true do + resources :members, only: %i[index create update] do collection do delete "by_principal/:principal_id", action: :destroy_by_principal get :autocomplete_for_member + get :menu, to: "members/menus#show" end end - namespace :members do - resource :menu, only: %[show] - end - resource :repository, controller: "repositories", except: [:new] do # Destroy uses a get request to prompt the user before the actual DELETE request get :destroy_info diff --git a/spec/controllers/members_controller_spec.rb b/spec/controllers/members_controller_spec.rb index 42d75416838..8a75102d7e9 100644 --- a/spec/controllers/members_controller_spec.rb +++ b/spec/controllers/members_controller_spec.rb @@ -259,7 +259,7 @@ RSpec.describe MembersController do describe "WHEN the user is not authorized" do it "is forbidden" do subject - expect(response.response_code).to eq(403) + expect(response.response_code).to eq(404) end end end @@ -476,6 +476,7 @@ RSpec.describe MembersController do let(:action) do post :update, params: { + project_id: project.id, id: member.id, member: { role_ids: [role2.id], user_id: user.id } } From 05df992b61441775ffb0b40b79d4404c17afd449 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Mon, 2 Feb 2026 14:56:35 +0100 Subject: [PATCH 186/293] Properly load news through scopes --- app/controllers/news/comments_controller.rb | 23 +++++++++--------- app/controllers/news_controller.rb | 21 +++++++--------- app/views/news/_news.html.erb | 2 +- app/views/news/edit.html.erb | 8 +++---- app/views/news/index.html.erb | 10 ++++---- app/views/news/show.html.erb | 10 ++++---- config/routes.rb | 8 +++---- .../news/comments_controller_spec.rb | 24 ++++++++++--------- spec/controllers/news_controller_spec.rb | 18 +++++++------- 9 files changed, 60 insertions(+), 64 deletions(-) diff --git a/app/controllers/news/comments_controller.rb b/app/controllers/news/comments_controller.rb index 76d090c430a..03d5def8a41 100644 --- a/app/controllers/news/comments_controller.rb +++ b/app/controllers/news/comments_controller.rb @@ -30,8 +30,9 @@ class News::CommentsController < ApplicationController default_search_scope :news - before_action :find_project_news_and_comment, only: %i[destroy] - before_action :find_news, only: %i[create] + + before_action :find_news_and_project + before_action :find_comment, only: [:destroy] before_action :authorize def create @@ -41,24 +42,22 @@ class News::CommentsController < ApplicationController flash[:notice] = I18n.t(:label_comment_added) end - redirect_to news_path(@news), status: :see_other + redirect_to project_news_path(@project, @news), status: :see_other end def destroy - @comment.destroy - redirect_to news_path(@news), status: :see_other + @comment.destroy! + redirect_to project_news_path(@project, @news), status: :see_other end private - def find_project_news_and_comment - @comment = Comment.find(params[:id]) - @news = News.visible.find(@comment.commented_id) - @project = @news.project + def find_comment + @comment = @news.comments.find(params[:id]) end - def find_news - @news = News.visible.find(params[:news_id]) - @project = @news.project + def find_news_and_project + @project = Project.visible.find(params[:project_id]) + @news = @project.news.visible.find(params[:news_id]) end end diff --git a/app/controllers/news_controller.rb b/app/controllers/news_controller.rb index f88309d064b..5bed0d7eca5 100644 --- a/app/controllers/news_controller.rb +++ b/app/controllers/news_controller.rb @@ -34,17 +34,16 @@ class NewsController < ApplicationController default_search_scope :news + before_action :load_and_authorize_in_optional_project before_action :find_news_object, except: %i[new create index] - before_action :find_project_from_association, except: %i[new create index] - before_action :find_project, only: %i[new create] - before_action :authorize, except: [:index] - before_action :load_and_authorize_in_optional_project, only: [:index] + before_action :authorize + accept_key_auth :index def index - scope = @project ? @project.news : News.all + scope = @project ? @project.news : News.visible - @newss = scope.merge(News.latest_for(current_user, count: 0)) + @news = scope.merge(News.latest_for(current_user, count: 0)) .page(page_param) .per_page(per_page_param) @@ -53,7 +52,7 @@ class NewsController < ApplicationController render locals: { menu_name: project_or_global_menu } end format.atom do - render_feed(@newss, + render_feed(@news, title: (@project ? @project.name : Setting.app_title) + ": #{I18n.t(:label_news_plural)}") end end @@ -95,7 +94,7 @@ class NewsController < ApplicationController if call.success? flash[:notice] = I18n.t(:notice_successful_update) - redirect_to action: "show", id: @news + redirect_to project_news_path(@project, @news) else @news = call.result render action: :edit, status: :unprocessable_entity @@ -119,10 +118,6 @@ class NewsController < ApplicationController private def find_news_object - @news = @object = News.find(params[:id].to_i) - end - - def find_project - @project = Project.find(params[:project_id]) + @news = @project.news.visible.find(params[:id].to_i) end end diff --git a/app/views/news/_news.html.erb b/app/views/news/_news.html.erb index c4dead767c9..bff2c0784cb 100644 --- a/app/views/news/_news.html.erb +++ b/app/views/news/_news.html.erb @@ -27,7 +27,7 @@ See COPYRIGHT and LICENSE files for more details. ++#%>
    - <%= link_to_project(news.project) + ": " unless @project %> + <%= "#{link_to_project(news.project)}: " unless @project %>

    <%= link_to h(news.title), news_path(news) %>

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

    diff --git a/app/views/news/edit.html.erb b/app/views/news/edit.html.erb index eff11a9e565..5cb2b0d7dbb 100644 --- a/app/views/news/edit.html.erb +++ b/app/views/news/edit.html.erb @@ -33,18 +33,18 @@ See COPYRIGHT and LICENSE files for more details. header.with_title { @news.title } header.with_breadcrumbs( [ - ({ href: project_overview_path(@project.id), text: @project.name } if @project), - ({ href: project_news_index_path(@project.id), text: t(:label_news_plural) } if @project), + { href: project_overview_path(@project.id), text: @project.name }, + { href: project_news_index_path(@project.id), text: t(:label_news_plural) }, @news.title ].compact ) end %> -<%= labelled_tabular_form_for @news, html: { id: "news-form" } do |f| %> +<%= labelled_tabular_form_for [@project, @news], html: { id: "news-form" } do |f| %> <%= render partial: "form", locals: { f: f } %>
    <%= styled_button_tag t(:button_save), class: "-primary -with-icon icon-checkmark" %> - <%= link_to t(:button_cancel), news_path(@news), class: "button -with-icon icon-cancel" %> + <%= link_to t(:button_cancel), project_news_path(@project, @news), class: "button -with-icon icon-cancel" %> <% end %>
    diff --git a/app/views/news/index.html.erb b/app/views/news/index.html.erb index 05352ffd934..7ebd448d9da 100644 --- a/app/views/news/index.html.erb +++ b/app/views/news/index.html.erb @@ -57,15 +57,15 @@ See COPYRIGHT and LICENSE files for more details. %> <% end %> -<% if @newss.any? %> - <% @newss.each do |news| %> +<% if @news.any? %> + <% @news.each do |news| %>
    -

    <%= avatar(news.author) %><%= link_to_project(news.project) + ": " unless news.project == @project %> +

    <%= 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.presence || truncate(news.description, length: 150, escape: false)), object: news) %> + <%= format_text(news.summary.presence || truncate(news.description, length: 150, escape: false), object: news) %>
    <% end %> @@ -80,7 +80,7 @@ See COPYRIGHT and LICENSE files for more details. <% end %> <% end %> -<%= pagination_links_full @newss %> +<%= pagination_links_full @news %> <%= other_formats_links do |f| %> <%= f.link_to "Atom", url: { project_id: @project, key: User.current.rss_key } %> diff --git a/app/views/news/show.html.erb b/app/views/news/show.html.erb index c4c1ffe0eda..3f8f8f54d9f 100644 --- a/app/views/news/show.html.erb +++ b/app/views/news/show.html.erb @@ -32,8 +32,8 @@ See COPYRIGHT and LICENSE files for more details. header.with_title { "#{avatar(@news.author)} #{h @news.title}".html_safe } header.with_breadcrumbs( [ - ({ href: project_overview_path(@project.id), text: @project.name } if @project), - ({ href: project_news_index_path(@project.id), text: t(:label_news_plural) } if @project), + { href: project_overview_path(@project.id), text: @project.name }, + { href: project_news_index_path(@project.id), text: t(:label_news_plural) }, @news.title ].compact ) @@ -43,7 +43,7 @@ See COPYRIGHT and LICENSE files for more details. mobile_icon: :pencil, mobile_label: t(:button_edit), size: :medium, - href: edit_news_path(@news), + href: edit_project_news_path(@news), aria: { label: I18n.t(:button_edit) }, title: I18n.t(:button_edit) ) do |button| @@ -58,7 +58,7 @@ See COPYRIGHT and LICENSE files for more details. header.with_action_button( scheme: :danger, tag: :a, - href: news_path(@news), + href: project_news_path(@project, @news), mobile_icon: :trash, mobile_label: I18n.t("button_delete"), data: { turbo_confirm: t(:text_are_you_sure), turbo_method: :delete }, @@ -100,7 +100,7 @@ See COPYRIGHT and LICENSE files for more details. <% end %>
    <% if authorize_for 'news/comments', 'create' %> - <%= form_for([@news, Comment.new], html: { id: "add_comment_form" }) do %> + <%= form_for([@project, @news, Comment.new], html: { id: "add_comment_form" }) do %>
    <%= label_tag "comment_comments", Journal.human_attribute_name(:notes), class: "sr-only" %> <%= text_area "comment", diff --git a/config/routes.rb b/config/routes.rb index 2c10c1d9c59..bc95e6391af 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -385,7 +385,9 @@ Rails.application.routes.draw do # this could probably be rewritten with a resource as: 'roadmap' get "/roadmap" => "versions#index" - resources :news, only: %i[index new create] + resources :news do + resources :comments, controller: "news/comments", only: %i[create destroy] + end # Match everything to be the ID of the wiki page except the part that # is reserved for the format. This assumes that we have only two formats: @@ -911,9 +913,7 @@ Rails.application.routes.draw do end end - resources :news, only: %i[index destroy update edit show] do - resources :comments, controller: "news/comments", only: %i[create destroy], shallow: true - end + resources :news, only: %i[index] # redirect for backwards compatibility scope "attachments", diff --git a/spec/controllers/news/comments_controller_spec.rb b/spec/controllers/news/comments_controller_spec.rb index b10d185f478..0e693cd2c3a 100644 --- a/spec/controllers/news/comments_controller_spec.rb +++ b/spec/controllers/news/comments_controller_spec.rb @@ -33,18 +33,20 @@ require "spec_helper" RSpec.describe News::CommentsController do render_views - let(:user) { create(:admin) } - let(:news) { create(:news) } + let(:user) { create(:admin) } + + let(:project) { create(:project) } + let(:news) { create(:news, project: project) } before do - allow(User).to receive(:current).and_return user + login_as(user) end describe "#create" do it "assigns a comment to the news item and redirects to the news page" do - post :create, params: { news_id: news.id, comment: { comments: "This is a test comment" } } + post :create, params: { project_id: project.id, news_id: news.id, comment: { comments: "This is a test comment" } } - expect(response).to redirect_to news_path(news) + expect(response).to redirect_to project_news_path(news.project, news) latest_comment = news.comments.reorder(created_at: :desc).first expect(latest_comment).not_to be_nil @@ -54,9 +56,9 @@ RSpec.describe News::CommentsController do it "doesn't create a comment when it is invalid" do expect do - post :create, params: { news_id: news.id, comment: { comments: "" } } - expect(response).to redirect_to news_path(news) - end.not_to change { Comment.count } + post :create, params: { project_id: project.id, news_id: news.id, comment: { comments: "" } } + expect(response).to redirect_to project_news_path(news.project, news) + end.not_to change(Comment, :count) end end @@ -65,10 +67,10 @@ RSpec.describe News::CommentsController do comment = create(:comment, commented: news) expect do - delete :destroy, params: { id: comment.id } - end.to change { Comment.count }.by -1 + delete :destroy, params: { project_id: project.id, news_id: news.id, id: comment.id } + end.to change(Comment, :count).by(-1) - expect(response).to redirect_to news_path(news) + expect(response).to redirect_to project_news_path(project, news) expect { comment.reload }.to raise_error ActiveRecord::RecordNotFound end end diff --git a/spec/controllers/news_controller_spec.rb b/spec/controllers/news_controller_spec.rb index dd35a60bb1f..6a63831755b 100644 --- a/spec/controllers/news_controller_spec.rb +++ b/spec/controllers/news_controller_spec.rb @@ -48,7 +48,7 @@ RSpec.describe NewsController do expect(response).to render_template "index" expect(assigns(:project)).to be_nil - expect(assigns(:newss)).not_to be_nil + expect(assigns(:news)).not_to be_nil end it "renders index with project" do @@ -56,13 +56,13 @@ RSpec.describe NewsController do expect(response).to be_successful expect(response).to render_template "index" - expect(assigns(:newss)).not_to be_nil + expect(assigns(:news)).not_to be_nil end end describe "#show" do it "renders show" do - get :show, params: { id: news.id } + get :show, params: { project_id: news.project_id, id: news.id } expect(response).to be_successful expect(response).to render_template "show" @@ -71,7 +71,7 @@ RSpec.describe NewsController do end it "renders show with slug" do - get :show, params: { id: "#{news.id}-some-news-title" } + get :show, params: { project_id: news.project_id, id: "#{news.id}-some-news-title" } expect(response).to be_successful expect(response).to render_template "show" @@ -80,7 +80,7 @@ RSpec.describe NewsController do end it "renders error if news item is not found" do - get :show, params: { id: -1 } + get :show, params: { project_id: news.project_id, id: -1 } expect(response).to be_not_found end @@ -141,7 +141,7 @@ RSpec.describe NewsController do describe "#edit" do it "renders edit" do - get :edit, params: { id: news.id } + get :edit, params: { project_id: news.project_id, id: news.id } expect(response).to be_successful expect(response).to render_template "edit" end @@ -150,9 +150,9 @@ RSpec.describe NewsController do describe "#update" do it "updates the news element" do put :update, - params: { id: news.id, news: { description: "Description changed by test_post_edit" } } + params: { project_id: news.project_id, id: news.id, news: { description: "Description changed by test_post_edit" } } - expect(response).to redirect_to news_path(news) + expect(response).to redirect_to project_news_path(news.project, news) news.reload expect(news.description).to eq "Description changed by test_post_edit" @@ -161,7 +161,7 @@ RSpec.describe NewsController do describe "#destroy" do it "deletes the news item and redirects with 303 See Other" do - delete :destroy, params: { id: news.id } + delete :destroy, params: { project_id: news.project_id, id: news.id } expect(response).to have_http_status(:see_other) expect(response).to redirect_to project_news_index_path(news.project) From 32569a675f8aba4d2643df5be8c168841044cf8c Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Mon, 2 Feb 2026 17:51:48 +0100 Subject: [PATCH 187/293] Fix forum routes & tests --- app/controllers/forums_controller.rb | 38 +- app/controllers/messages_controller.rb | 10 +- config/routes.rb | 16 +- spec/controllers/forums_controller_spec.rb | 413 ++++++++----------- spec/controllers/messages_controller_spec.rb | 136 ++---- spec/requests/messages/destroy_spec.rb | 6 +- 6 files changed, 237 insertions(+), 382 deletions(-) diff --git a/app/controllers/forums_controller.rb b/app/controllers/forums_controller.rb index 3d70f09fccf..91fccd142af 100644 --- a/app/controllers/forums_controller.rb +++ b/app/controllers/forums_controller.rb @@ -30,10 +30,12 @@ class ForumsController < ApplicationController default_search_scope :messages - before_action :find_project_by_project_id, - :authorize + before_action :find_project_by_project_id before_action :new_forum, only: %i[new create] before_action :find_forum, only: %i[show edit update move destroy] + + before_action :authorize + accept_key_auth :show include SortHelper @@ -59,11 +61,11 @@ class ForumsController < ApplicationController @message = Message.new render action: "show", layout: !request.xhr? end - format.json do - set_topics - - render template: "messages/index" - end + # The JSON template does not exist anymore, this never rendered + # format.json do + # set_topics + # render template: "messages/index" + # end format.atom do @messages = @forum .messages @@ -92,7 +94,7 @@ class ForumsController < ApplicationController def create if @forum.save flash[:notice] = I18n.t(:notice_successful_create) - redirect_to action: "index" + redirect_to project_forums_path(@project) else render :new end @@ -101,26 +103,24 @@ class ForumsController < ApplicationController def update if @forum.update(permitted_params.forum) flash[:notice] = I18n.t(:notice_successful_update) - redirect_to action: "index" + redirect_to project_forums_path(@project) else - render :edit + render :edit, status: :unprocessable_entity end end def move - if @forum.update(permitted_params.forum_move) - flash[:notice] = t(:notice_successful_update) - else - flash.now[:error] = t("forum_could_not_be_saved") - render action: :edit, status: :unprocessable_entity - end - redirect_to action: "index" + @forum.update!(permitted_params.forum_move) + + flash[:notice] = t(:notice_successful_update) + redirect_to project_forums_path(@project) end def destroy - @forum.destroy + @forum.destroy! + flash[:notice] = I18n.t(:notice_successful_delete) - redirect_to action: "index", status: :see_other + redirect_to project_forums_path(@project) end private diff --git a/app/controllers/messages_controller.rb b/app/controllers/messages_controller.rb index 7ce3158c4f3..ee612764f44 100644 --- a/app/controllers/messages_controller.rb +++ b/app/controllers/messages_controller.rb @@ -104,7 +104,7 @@ class MessagesController < ApplicationController if call.success? call_hook(:controller_messages_reply_after_save, params:, message: @reply) end - redirect_to topic_path(@topic, r: @reply) + redirect_to project_forum_topic_path(@project, @forum, @topic, r: @reply) end # Edit a message @@ -117,7 +117,7 @@ class MessagesController < ApplicationController if call.success? flash[:notice] = t(:notice_successful_update) @message.reload - redirect_to topic_path(@message.root, r: @message.parent_id && @message.id) + redirect_to project_forum_topic_path(@project, @forum, @message.root, r: @message.parent_id && @message.id) else render action: :edit, status: :unprocessable_entity end @@ -131,9 +131,9 @@ class MessagesController < ApplicationController @message.destroy flash[:notice] = t(:notice_successful_delete) redirect_target = if @message.parent.nil? - { controller: "/forums", action: "show", project_id: @project, id: @forum } + project_forum_path(@project, @forum) else - { action: "show", id: @message.parent, r: @message } + project_forum_topic_path(@project, @forum, @message.parent, r: @message) end redirect_to redirect_target, status: :see_other @@ -157,7 +157,7 @@ class MessagesController < ApplicationController private def find_project_forum_and_message - @message = Message.visible(find_params[:id]) + @message = Message.visible.find(params[:id]) @forum = @message.forum @project = @forum.project end diff --git a/config/routes.rb b/config/routes.rb index bc95e6391af..7c2922ce351 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -458,6 +458,13 @@ Rails.application.routes.draw do end resources :forums do + resources :topics, controller: "messages", except: [:index] do + member do + get :quote + post :reply, as: "reply_to" + end + end + member do get :confirm_destroy get :move @@ -904,15 +911,6 @@ Rails.application.routes.draw do # The show page of groups is public and thus moved out of the admin scope resources :groups, only: %i[show], as: :show_group - resources :forums, only: [] do - resources :topics, controller: "messages", except: [:index], shallow: true do - member do - get :quote - post :reply, as: "reply_to" - end - end - end - resources :news, only: %i[index] # redirect for backwards compatibility diff --git a/spec/controllers/forums_controller_spec.rb b/spec/controllers/forums_controller_spec.rb index 6fdb1caf1e2..4b54e9b2014 100644 --- a/spec/controllers/forums_controller_spec.rb +++ b/spec/controllers/forums_controller_spec.rb @@ -31,77 +31,115 @@ require "spec_helper" RSpec.describe ForumsController do - shared_let(:user) { create(:user) } - let(:project) { create(:project) } + let(:permissions) { %i[view_messages] } + let(:project_role) { create(:project_role, permissions:, add_public_permissions: false) } + + let(:project) { create(:project, enabled_module_names: ["forums"]) } + let(:user) { create(:user, member_with_roles: { project => project_role }) } let!(:forum) { create(:forum, project:) } before do - disable_flash_sweep + login_as(user) end describe "#index" do - context "public project" do - let(:project) { create(:public_project) } - let!(:role) { create(:non_member) } + let(:other_project) { create(:project, member_with_permissions: { user => permissions }) } + let!(:forum_in_other_project) { create(:forum, project: other_project) } - it "renders the index template" do - as_logged_in_user(user) do - get :index, params: { project_id: project.id } - end + it "renders the index template with the requested forum" do + get :index, params: { project_id: project.id } - expect(response).to be_successful - expect(response).to render_template "forums/index" - expect(assigns(:forums)).to be_present - expect(assigns(:project)).to be_present - end + expect(response).to be_successful + expect(response).to render_template("forums/index") + expect(assigns(:forums)).to contain_exactly(forum) + expect(assigns(:project)).to eq(project) end - context "assuming authorized" do - it "renders the index template" do - as_logged_in_user(user) do - allow(@controller).to receive(:authorize).and_return(true) - get :index, params: { project_id: project.id } - end - expect(response).to be_successful - end - end + context "when user does not have permission to view forums" do + let(:permissions) { [:view_project] } - context "when login_required", with_settings: { login_required: true } do - it "redirects to login" do - get :index, params: { project_id: "not found" } - expect(response).to redirect_to signin_path(back_url: project_forums_url("not found")) - end - end - - context "when not login_required", with_settings: { login_required: false } do - it "renders 404 for not found" do - get :index, params: { project_id: "not found" } - expect(response).to have_http_status :not_found + it "renders 403 forbidden" do + get :index, params: { project_id: project.id } + expect(response).to have_http_status(:forbidden) end end end describe "#show" do - before do - allow(project).to receive_message_chain(:forums, :find).and_return(forum) - allow(@controller).to receive(:authorize) - allow(@controller).to receive(:find_project_by_project_id) do - @controller.instance_variable_set(:@project, project) + it "renders the show template with the requested forum" do + get :show, params: { project_id: project.id, id: forum.id } + + expect(response).to be_successful + expect(response).to render_template("forums/show") + expect(assigns(:forum)).to eq(forum) + expect(assigns(:project)).to eq(project) + expect(assigns(:message)).to be_a_new(Message) + end + + context "when user does not have permission to view messages" do + let(:permissions) { [:view_project] } + + it "renders 403 forbidden" do + get :show, params: { project_id: project.id, id: forum.id } + expect(response).to have_http_status(:forbidden) end end - context "when login_required", with_settings: { login_required: true } do - it "redirects to login" do - get :show, params: { project_id: project.id, id: 1 } - expect(response).to redirect_to signin_path(back_url: project_forum_url(project.id, 1)) + describe "with some messages messages" do + let!(:message1) { create(:message, forum:, updated_at: 1.minute.ago) } + let!(:message2) { create(:message, forum:, updated_at: 4.minutes.ago) } + let!(:sticked_message1) do + create(:message, forum_id: forum.id, + subject: "How to", + content: "How to install this cool app", + sticky: true, + updated_at: 2.minutes.ago, + sticked_on: 2.minutes.ago) end - end - context "when not login_required", with_settings: { login_required: false } do - it "renders the show template" do - get :show, params: { project_id: project.id, id: 1 } - expect(response).to be_successful - expect(response).to render_template "forums/show" + let!(:sticked_message2) do + create(:message, forum_id: forum.id, + subject: "FAQ", + content: "Frequestly asked question", + sticky: true, + updated_at: 10.minutes.ago, + sticked_on: 10.minutes.ago) + end + + it "displays the messages in the correct order, moving stickies to the top" do + get :show, params: { project_id: project.id, id: forum.id } + + expect(assigns(:topics)).to eq([ + sticked_message2, + sticked_message1, + message1, + message2 + ]) + end + + context "when requesting JSON format" do + it "renders the messages in the correct order as JSON" do + # JSON rendering was disfunctional because the template does not exist + + expect do + get :show, params: { project_id: project.id, id: forum.id }, format: :json + end.to raise_error(ActionController::UnknownFormat) + end + end + + context "when requesting ATOM format" do + it "renders the messages in the correct order as ATOM" do + get :show, params: { project_id: project.id, id: forum.id }, format: :atom + + expect(response).to be_successful + expect(response.content_type).to eq("application/atom+xml; charset=utf-8") + expect(assigns(:messages)).to eq([ + sticked_message2, + sticked_message1, + message1, + message2 + ]) + end end end end @@ -110,238 +148,119 @@ RSpec.describe ForumsController do let(:params) { { project_id: project.id, forum: forum_params } } let(:forum_params) { { name: "my forum", description: "awesome forum" } } - before do - expect(@controller).to receive(:authorize) - expect(@controller).to receive(:find_project_by_project_id) do - @controller.instance_variable_set(:@project, project) - end - - # parameter expectation needs to have strings as keys - expect(Forum) - .to receive(:new) - .with(ActionController::Parameters.new(forum_params).permit!) - .and_return(forum) - end - - describe "w/ the params being valid" do - before do - expect(forum).to receive(:save).and_return(true) - - as_logged_in_user user do - post :create, params: - end - end - - it "redirects to the index page if successful" do - expect(response) - .to redirect_to controller: "/forums", - action: "index", - project_id: project.id - end - - it "have a successful creation flash" do - expect(flash[:notice]).to eq(I18n.t(:notice_successful_create)) + context "when the user is not allowed to manage forums" do + it "renders 403 forbidden" do + post :create, params: params + expect(response).to have_http_status(:forbidden) end end - describe "w/ the params being invalid" do - before do - expect(forum).to receive(:save).and_return(false) + context "when the user is allowed to manage forums" do + let(:permissions) { %i[view_messages manage_forums] } - as_logged_in_user user do - post :create, params: + describe "with valid params" do + it "creates a new forum and redirects to index" do + expect do + post :create, params: + end.to change(Forum, :count).by(1) + + expect(response).to redirect_to project_forums_path(project) + expect(flash[:notice]).to eq(I18n.t(:notice_successful_create)) end end - it "renders the new template" do - expect(response).to render_template("new") + describe "with invalid params" do + let(:forum_params) { { name: "", description: "awesome forum" } } + + it "renders the new template" do + expect do + post :create, params: + end.not_to change(Forum, :count) + + expect(response).to render_template("new") + end end end end - describe "#destroy", with_settings: { login_required: false } do - let(:forum_params) { { name: "my forum", description: "awesome forum" } } - - before do - expect(@controller).to receive(:authorize) - expect(project).to receive_message_chain(:forums, :find).and_return(forum) - expect(@controller).to receive(:find_project_by_project_id) do - @controller.instance_variable_set(:@project, project) + describe "#destroy" do + context "when the user is not allowed to manage forums" do + it "renders 403 forbidden" do + delete :destroy, params: { project_id: project.id, id: forum.id } + expect(response).to have_http_status(:forbidden) end end - it "deletes the forum and redirects with 303 See Other" do - expect(forum).to receive(:destroy) - delete :destroy, params: { project_id: project.identifier, id: 1 } - expect(response).to have_http_status(:see_other) - expect(response).to redirect_to(project_forums_path(project)) + context "when the user is allowed to manage forums" do + let(:permissions) { %i[view_messages manage_forums] } + + it "deletes the forum and redirects to index" do + expect do + delete :destroy, params: { project_id: project.id, id: forum.id } + end.to change(Forum, :count).by(-1) + + expect(response).to redirect_to project_forums_path(project) + expect(flash[:notice]).to eq(I18n.t(:notice_successful_delete)) + end end end describe "#move" do - let(:project) { create(:project) } - let!(:forum_1) do - create(:forum, - project:, - position: 1) - end - let!(:forum_2) do - create(:forum, - project:, - position: 2) + let!(:forum) { create(:forum, project: project, position: 1) } + let!(:forum2) { create(:forum, project: project, position: 2) } + let!(:forum3) { create(:forum, project: project, position: 3) } + + context "when the user is not allowed to manage forums" do + it "renders 403 forbidden" do + post :move, params: { project_id: project.id, id: forum3.id, forum: { move_to: "higher" } } + expect(response).to have_http_status(:forbidden) + end end - before do - allow(@controller).to receive(:authorize).and_return(true) - end + context "when the user is allowed to manage forums" do + let(:permissions) { %i[view_messages manage_forums] } - describe "#higher", with_settings: { login_required: false } do - let(:move_to) { "higher" } + it "moves the forum and redirects to index" do + post :move, params: { project_id: project.id, id: forum3.id, forum: { move_to: "higher" } } - before do - post "move", params: { id: forum_2.id, - project_id: forum_2.project_id, - forum: { move_to: } } - end + expect(response).to redirect_to project_forums_path(project) + expect(flash[:notice]).to eq(I18n.t(:notice_successful_update)) - it do - expect(forum_2.reload.position).to eq(1) - end - - it do - expect(response).to be_redirect - end - - it do - expect(response) - .to redirect_to controller: "/forums", - action: "index", - project_id: project.id + expect(forum.reload.position).to eq(1) + expect(forum2.reload.position).to eq(3) + expect(forum3.reload.position).to eq(2) end end end describe "#update" do - let!(:forum) do - create(:forum, name: "Forum name", - description: "Forum description") + context "when the user is not allowed to manage forums" do + it "renders 403 forbidden" do + patch :update, params: { project_id: project.id, id: forum.id, forum: { name: "Updated Forum" } } + expect(response).to have_http_status(:forbidden) + end end - before do - expect(@controller).to receive(:authorize) - end + context "when the user is allowed to manage forums" do + let(:permissions) { %i[view_messages manage_forums] } - describe "w/ the params being valid" do - before do - as_logged_in_user user do - put :update, params: { id: forum.id, - project_id: forum.project_id, - forum: { name: "New name", description: "New description" } } + describe "with valid params" do + it "updates the forum and redirects to index" do + patch :update, params: { project_id: project.id, id: forum.id, forum: { name: "Updated Forum" } } + + expect(response).to redirect_to project_forums_path(project) + expect(flash[:notice]).to eq(I18n.t(:notice_successful_update)) + expect(forum.reload.name).to eq("Updated Forum") end end - it "redirects to the index page if successful" do - expect(response).to redirect_to controller: "/forums", - action: "index", - project_id: forum.project_id - end + describe "with invalid params" do + it "renders the edit template" do + expect do + patch :update, params: { project_id: project.id, id: forum.id, forum: { name: "" } } + end.not_to change { forum.reload.name } - it "have a successful update flash" do - expect(flash[:notice]).to eq(I18n.t(:notice_successful_update)) - end - - it "changes the database entry" do - forum.reload - expect(forum.name).to eq("New name") - expect(forum.description).to eq("New description") - end - end - - describe "w/ the params being invalid" do - before do - as_logged_in_user user do - post :update, params: { id: forum.id, - project_id: forum.project_id, - forum: { name: "", description: "New description" } } - end - end - - it "renders the edit template" do - expect(response).to render_template("edit") - end - - it "does not change the database entry" do - forum.reload - expect(forum.name).to eq("Forum name") - expect(forum.description).to eq("Forum description") - end - end - end - - describe "#sticky", with_settings: { login_required: false } do - let!(:message1) { create(:message, forum:) } - let!(:message2) { create(:message, forum:) } - let!(:sticked_message1) do - create(:message, forum_id: forum.id, - subject: "How to", - content: "How to install this cool app", - sticky: "1", - sticked_on: Time.now - 2.minutes) - end - - let!(:sticked_message2) do - create(:message, forum_id: forum.id, - subject: "FAQ", - content: "Frequestly asked question", - sticky: "1", - sticked_on: - Time.now - 1.minute) - end - - describe "all sticky messages" do - before do - expect(@controller).to receive(:authorize) - get :show, params: { project_id: project.id, id: forum.id } - end - - it "renders show" do - expect(response).to render_template "show" - end - - it "is displayed on top" do - expect(assigns[:topics][0].id).to eq(sticked_message1.id) - end - end - - describe "edit a sticky message" do - before do - sticked_message1.sticky = 0 - sticked_message1.save! - end - - describe "when sticky is unset from message" do - before do - expect(@controller).to receive(:authorize) - get :show, params: { project_id: project.id, id: forum.id } - end - - it "is not displayed as sticky message" do - expect(sticked_message1.sticked_on).to be_nil - expect(assigns[:topics][0].id).not_to eq(sticked_message1.id) - end - end - - describe "when sticky is set back to message" do - before do - sticked_message1.sticky = 1 - sticked_message1.save! - - expect(@controller).to receive(:authorize) - get :show, params: { project_id: project.id, id: forum.id } - end - - it "is not displayed on first position" do - expect(assigns[:topics][0].id).to eq(sticked_message2.id) + expect(response).to render_template("edit") end end end diff --git a/spec/controllers/messages_controller_spec.rb b/spec/controllers/messages_controller_spec.rb index 1f28e69b444..3acf37daf8b 100644 --- a/spec/controllers/messages_controller_spec.rb +++ b/spec/controllers/messages_controller_spec.rb @@ -32,47 +32,29 @@ require "spec_helper" RSpec.describe MessagesController, with_settings: { journal_aggregation_time_minutes: 0 } do let(:user) { create(:user) } - let(:project) { create(:project) } - let(:role) { create(:project_role) } - let!(:member) do - create(:member, - project:, - principal: user, - roles: [role]) - end - let!(:forum) do - create(:forum, - project:) - end + let(:permissions) { [] } + let(:project) { create(:project, member_with_permissions: { user => permissions }) } + let!(:forum) { create(:forum, project:) } let(:filename) { "testfile.txt" } - let(:file) { File.open(Rails.root.join("spec/fixtures/files", filename)) } + let(:file) { Rails.root.join("spec/fixtures/files", filename).open } - before { allow(User).to receive(:current).and_return user } + before do + login_as(user) + end describe "#show" do - context "with a public project" do - let(:user) { User.anonymous } - let(:project) { create(:public_project) } + context "when the user is allowed to view messages" do + let(:permissions) { %i[view_messages] } let!(:message) { create(:message, forum:) } - context "when login_required", with_settings: { login_required: true } do - it "redirects to login" do - get :show, params: { project_id: project.id, id: message.id } - expect(response).to redirect_to signin_path(back_url: topic_url(message.id)) - end - end + it "renders the show template" do + get :show, params: { project_id: project.id, forum_id: forum.id, id: message.id } - context "when not login_required", with_settings: { login_required: false } do - it "renders the show template" do - get :show, params: { project_id: project.id, id: message.id } - - expect(response).to be_successful - expect(response).to render_template "messages/show" - expect(assigns(:topic)).to be_present - expect(assigns(:forum)).to be_present - expect(assigns(:project)).to be_present - end + expect(response).to render_template "messages/show" + expect(assigns(:topic)).to be_present + expect(assigns(:forum)).to be_present + expect(assigns(:project)).to be_present end end end @@ -80,10 +62,12 @@ RSpec.describe MessagesController, with_settings: { journal_aggregation_time_min describe "#update" do let(:message) { create(:message, forum:) } let(:other_forum) { create(:forum, project:) } + let(:permissions) { %i[edit_messages] } before do - role.add_permission!(:edit_messages) and user.reload - put :update, params: { id: message, + put :update, params: { project_id: project.id, + forum_id: forum.id, + id: message, message: { forum_id: other_forum } } end @@ -91,76 +75,34 @@ RSpec.describe MessagesController, with_settings: { journal_aggregation_time_min expect(message.reload.forum).to eq(other_forum) end - context "attachment upload" do - let!(:message) { create(:message) } - let(:attachment_id) { "attachments_#{message.attachments.first.id}" } + context "when uploading an attachment" do + let!(:message) { create(:message, forum: forum) } + let!(:attachment) { create(:attachment, container: nil, author: user) } + let(:attachment_id) { "attachments_#{attachment.id}" } # Attachment is already uploaded - let(:attachment) { create(:attachment, container: nil, author: user) } let(:params) do - { id: message.id, - attachments: { "0" => { "id" => attachment.id } } } + { + project_id: project.id, + forum_id: forum.id, + id: message.id, + attachments: { "0" => { "id" => attachment.id } } + } end - describe "add" do - before do - allow_any_instance_of(Message).to receive(:editable_by?).and_return(true) - end + describe "when adding an attachment" do + let(:permissions) { %i[edit_messages] } - context "journal" do + context "with journaling" do before do put(:update, params:) message.reload end - describe "#key" do - subject { message.journals.last.details } - - it { is_expected.to have_key attachment_id } + it "stores attachment details in the journal entry" do + expect(message.journals.last.details).to have_key attachment_id + expect(message.journals.last.details[attachment_id].last).to eq(attachment.filename) end - - describe "#value" do - subject { message.journals.last.details[attachment_id].last } - - it { is_expected.to eq(attachment.filename) } - end - end - end - end - - describe "#remove" do - let!(:attachment) do - create(:attachment, - container: message, - author: user, - filename:) - end - let!(:attachable_journal) do - create(:journal_attachable_journal, - journal: message.journals.last, - attachment:, - filename:) - end - - before do - message.reload - message.attachments.delete(attachment) - message.reload - end - - context "journal" do - let(:attachment_id) { "attachments_#{attachment.id}" } - - describe "#key" do - subject { message.journals.last.details } - - it { is_expected.to have_key attachment_id } - end - - describe "#value" do - subject { message.journals.last.details[attachment_id].first } - - it { is_expected.to eq(filename) } end end end @@ -172,12 +114,8 @@ RSpec.describe MessagesController, with_settings: { journal_aggregation_time_min context "when allowed" do let(:user) { create(:admin) } - before do - login_as user - end - it "renders the content as json" do - get :quote, params: { forum_id: forum.id, id: message.id }, format: :json + get :quote, params: { project_id: project.id, forum_id: forum.id, id: message.id }, format: :json expect(response).to be_successful expect(response.body).to eq '{"subject":"RE: subject","content":" wrote:\n\u003e foo\n\n"}' @@ -189,7 +127,7 @@ RSpec.describe MessagesController, with_settings: { journal_aggregation_time_min user.save! validate: false message.update!(author: user) - get :quote, params: { forum_id: forum.id, id: message.id }, format: :json + get :quote, params: { project_id: project.id, forum_id: forum.id, id: message.id }, format: :json expect(response).to be_successful expect(response.parsed_body["content"]).to eq "Hello <b>world</b> wrote:\n> foo\n\n" diff --git a/spec/requests/messages/destroy_spec.rb b/spec/requests/messages/destroy_spec.rb index 1a6653683da..beaa23ba255 100644 --- a/spec/requests/messages/destroy_spec.rb +++ b/spec/requests/messages/destroy_spec.rb @@ -40,7 +40,7 @@ RSpec.describe "Messages destroy redirect", context "when an admin deletes a message" do current_user { create(:admin) } - let(:request) { delete "/topics/#{message.id}" } + let(:request) { delete "/projects/#{project.id}/forums/#{forum.id}/topics/#{message.id}" } subject do request @@ -62,7 +62,7 @@ RSpec.describe "Messages destroy redirect", current_user { create(:admin) } - let(:request) { delete "/topics/#{reply.id}" } + let(:request) { delete "/projects/#{project.id}/forums/#{forum.id}/topics/#{reply.id}" } subject do request @@ -71,7 +71,7 @@ RSpec.describe "Messages destroy redirect", it "responds with 303 See Other and redirects to the topic" do expect(subject).to have_http_status(:see_other) - expect(response).to redirect_to(topic_path(topic, r: reply)) + expect(response).to redirect_to(project_forum_topic_path(project, forum, topic, r: reply)) expect { Message.find(reply.id) }.to raise_error(ActiveRecord::RecordNotFound) expect { Message.find(topic.id) }.not_to raise_error From 82c6e34dcc4c93faaa5a846503f600fc3c79aa89 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Mon, 2 Feb 2026 18:19:57 +0100 Subject: [PATCH 188/293] Fix members specs and remove redundant specs --- spec/controllers/members_controller_spec.rb | 75 +++------------------ spec/routing/members_spec.rb | 7 +- 2 files changed, 15 insertions(+), 67 deletions(-) diff --git a/spec/controllers/members_controller_spec.rb b/spec/controllers/members_controller_spec.rb index 8a75102d7e9..374d82a92f9 100644 --- a/spec/controllers/members_controller_spec.rb +++ b/spec/controllers/members_controller_spec.rb @@ -44,62 +44,6 @@ RSpec.describe MembersController do before { login_as(admin) } - describe "create" do - shared_let(:admin) { create(:admin) } - let(:project2) { create(:project) } - - it "works for multiple users" do - post :create, - params: { - project_id: project2.identifier, - member: { - user_ids: [admin.id, user.id], - role_ids: [role.id] - } - } - - expect(response.response_code).to be < 400 - - [admin, user].each do |u| - u.reload - expect(u.memberships.size).to be >= 1 - - expect(u.memberships.find do |m| - expect(m.roles).to include(role) - end).not_to be_nil - end - end - end - - describe "update" do - shared_let(:admin) { create(:admin) } - let(:project2) { create(:project) } - let(:role1) { create(:project_role) } - let(:role2) { create(:project_role) } - let(:member2) do - create( - :member, - project: project2, - user: admin, - roles: [role1] - ) - end - - it "however allows roles to be updated through mass assignment" do - put "update", - params: { - project_id: project.identifier, - id: member2.id, - member: { - role_ids: [role1.id, role2.id] - } - } - - expect(Member.find(member2.id).roles).to include(role1, role2) - expect(response.response_code).to be < 400 - end - end - describe "#autocomplete_for_member" do let(:params) { { "project_id" => project.identifier.to_s, "q" => query } } let(:query) { "" } @@ -107,14 +51,15 @@ RSpec.describe MembersController do let(:global_permissions) { [] } let(:project_permissions) { [] } - subject { post(:autocomplete_for_member, xhr: true, params:) } + let(:user) do + create(:user, + member_with_permissions: { project => project_permissions }, + global_permissions: global_permissions) + end + + subject { post(:autocomplete_for_member, xhr: true, params:, format: :json) } before do - mock_permissions_for(user) do |mock| - mock.allow_globally(*global_permissions) - mock.allow_in_project(*project_permissions, project:) - end - login_as(user) end @@ -189,7 +134,9 @@ RSpec.describe MembersController do let!(:other_project) { create(:project) } let!(:other_user) { create(:user, member_with_permissions: { other_project => %i[view_project] }) } let!(:user) do - create(:user, member_with_permissions: { project => project_permissions, other_project => %i[view_project] }) + create(:user, + global_permissions: global_permissions, + member_with_permissions: { project => project_permissions, other_project => %i[view_project] }) end context "when the user is not authorized to see email addresses" do @@ -259,7 +206,7 @@ RSpec.describe MembersController do describe "WHEN the user is not authorized" do it "is forbidden" do subject - expect(response.response_code).to eq(404) + expect(response).to have_http_status(:forbidden) end end end diff --git a/spec/routing/members_spec.rb b/spec/routing/members_spec.rb index df40757e78e..5fe6fd416a2 100644 --- a/spec/routing/members_spec.rb +++ b/spec/routing/members_spec.rb @@ -47,9 +47,10 @@ RSpec.describe MembersController do end it { - expect(subject).to route(:put, "/members/5234").to(controller: "members", - action: "update", - id: "5234") + expect(subject).to route(:put, "/projects/1234/members/5234").to(controller: "members", + action: "update", + project_id: "1234", + id: "5234") } it { From d51f88189d859088259bc6141e129a093ba592e7 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 3 Feb 2026 10:13:00 +0100 Subject: [PATCH 189/293] Fix version controller overrides for backlogs --- .../backlogs/patches/versions_controller_patch.rb | 15 +++++++++++++-- .../spec/controllers/versions_controller_spec.rb | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/modules/backlogs/lib/open_project/backlogs/patches/versions_controller_patch.rb b/modules/backlogs/lib/open_project/backlogs/patches/versions_controller_patch.rb index b3b741750d3..2898f4e0699 100644 --- a/modules/backlogs/lib/open_project/backlogs/patches/versions_controller_patch.rb +++ b/modules/backlogs/lib/open_project/backlogs/patches/versions_controller_patch.rb @@ -33,9 +33,20 @@ module OpenProject::Backlogs::Patches::VersionsControllerPatch helper :version_settings - before_action :add_project_to_version_settings_attributes, only: %i[update create] + before_action :override_project_from_id, only: %i[edit update] - before_action :whitelist_update_params, only: :update + append_before_action :add_project_to_version_settings_attributes, only: %i[update create] + append_before_action :whitelist_update_params, only: :update + + private + + def override_project_from_id + # @project is already set by the VersionsController's find_version before action to the version's project + # here we want to add that we always set it to the project from params if present + if params[:project_id].present? + @project = Project.visible.find(params[:project_id]) + end + end def whitelist_update_params if @project != @version.project diff --git a/modules/backlogs/spec/controllers/versions_controller_spec.rb b/modules/backlogs/spec/controllers/versions_controller_spec.rb index d17a10e1116..197ac886638 100644 --- a/modules/backlogs/spec/controllers/versions_controller_spec.rb +++ b/modules/backlogs/spec/controllers/versions_controller_spec.rb @@ -28,7 +28,7 @@ require "spec_helper" -RSpec.describe VersionsController do +RSpec.describe VersionsController, "Backlog patches" do let(:version) do create(:version, sharing: "system") From 439462122fa7480863da7a092756a3ca276ae9f4 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 3 Feb 2026 10:31:05 +0100 Subject: [PATCH 190/293] Fix `work_package_not_found_result` method returning an unprocessable result --- modules/bim/app/services/bim/bcf/issues/create_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/bim/app/services/bim/bcf/issues/create_service.rb b/modules/bim/app/services/bim/bcf/issues/create_service.rb index 59cfcf61396..c1aede3af9b 100644 --- a/modules/bim/app/services/bim/bcf/issues/create_service.rb +++ b/modules/bim/app/services/bim/bcf/issues/create_service.rb @@ -89,7 +89,7 @@ module Bim::Bcf def work_package_not_found_result ServiceResult.failure(errors: Bim::Bcf::Issue.new.errors).tap do |r| - r.errors.add :work_package, :does_not_exist + r.errors.add :base, :error_not_found end end From 3bbb04bc1b35db8864248a090c5a984959d2dfe5 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 3 Feb 2026 10:34:31 +0100 Subject: [PATCH 191/293] We return 404 instead of 403 not leaking existing --- .../api/cost_entries/cost_entry_resource_spec.rb | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/modules/costs/spec/requests/api/cost_entries/cost_entry_resource_spec.rb b/modules/costs/spec/requests/api/cost_entries/cost_entry_resource_spec.rb index 0566b73ae18..7224367bc8e 100644 --- a/modules/costs/spec/requests/api/cost_entries/cost_entry_resource_spec.rb +++ b/modules/costs/spec/requests/api/cost_entries/cost_entry_resource_spec.rb @@ -36,10 +36,9 @@ RSpec.describe "API v3 Cost Entry resource" do include API::V3::Utilities::PathHelper let(:current_user) do - create(:user, member_with_roles: { project => role }) + create(:user, member_with_permissions: { project => permissions }) end let(:cost_entry) { create(:cost_entry, entity: work_package, user: entry_user) } - let(:role) { create(:project_role, permissions:) } let(:permissions) { [:view_cost_entries] } let(:project) { create(:project) } let(:work_package) { create(:work_package, project:) } @@ -73,11 +72,8 @@ RSpec.describe "API v3 Cost Entry resource" do context "when user can only see own cost entries" do let(:permissions) { [:view_own_cost_entries] } - context "when ost entry is not his own" do - it_behaves_like "error response", - 403, - "MissingPermission", - I18n.t("api_v3.errors.code_403") + context "when cost entry is not his own" do + it_behaves_like "not found" end context "when cost entry is their own" do @@ -95,10 +91,7 @@ RSpec.describe "API v3 Cost Entry resource" do describe "he can't even see own cost entries" do let(:entry_user) { current_user } - it_behaves_like "error response", - 403, - "MissingPermission", - I18n.t("api_v3.errors.code_403") + it_behaves_like "not found" end end end From 6dc0bbd4d400e63d2dac1ad407ff27cc5a60c9ca Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 3 Feb 2026 10:38:45 +0100 Subject: [PATCH 192/293] Fix specs in projects controller --- spec/controllers/projects_controller_spec.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index a7787a9c844..04c511faaeb 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -478,7 +478,10 @@ RSpec.describe ProjectsController do let(:service_result) { ServiceResult.new(success:) } before do - allow(Project).to receive(:find).and_return(project) + visible_relation = instance_double(ActiveRecord::Relation) + allow(Project).to receive(:visible).and_return(visible_relation) + allow(visible_relation).to receive(:find).with(project.id.to_s).and_return(project) + deletion_service = instance_double(Projects::ScheduleDeletionService, call: service_result) @@ -550,9 +553,9 @@ RSpec.describe ProjectsController do context "as a non-admin without copy_projects permissions" do let(:user) { build_stubbed(:user) } - it "returns 403 Not Authorized" do + it "returns 404 Not Found" do expect(response).not_to be_successful - expect(response).to have_http_status :forbidden + expect(response).to have_http_status :not_found end end end From 7d47953e1d086b077b7b3b20a0b05384cdc38c2a Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 3 Feb 2026 10:48:41 +0100 Subject: [PATCH 193/293] Fix specs on status controller --- spec/controllers/projects/status_controller_spec.rb | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/spec/controllers/projects/status_controller_spec.rb b/spec/controllers/projects/status_controller_spec.rb index fcbfe57a55d..3d78ce4f2ce 100644 --- a/spec/controllers/projects/status_controller_spec.rb +++ b/spec/controllers/projects/status_controller_spec.rb @@ -34,15 +34,10 @@ RSpec.describe Projects::StatusController do shared_let(:user) { create(:admin) } current_user { user } - let(:project) { build_stubbed(:project) } + let(:project) { create(:project) } let(:service_result) { ServiceResult.failure } before do - allow(Project) - .to receive(:find) - .with(project.identifier) - .and_return(project) - update_service = instance_double(Projects::UpdateService, call: service_result) allow(Projects::UpdateService) From 99b6ff19efcf210c4bea17c99980d620e67e96aa Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 3 Feb 2026 10:56:15 +0100 Subject: [PATCH 194/293] fix specs for repositories controller --- .../repositories_controller_spec.rb | 43 ++++++++----------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/spec/controllers/repositories_controller_spec.rb b/spec/controllers/repositories_controller_spec.rb index 2b6111d848c..de51551472d 100644 --- a/spec/controllers/repositories_controller_spec.rb +++ b/spec/controllers/repositories_controller_spec.rb @@ -31,15 +31,9 @@ require "spec_helper" RSpec.describe RepositoriesController do - let(:project) do - project = create(:project) - allow(Project).to receive(:find).and_return(project) - project - end - let(:user) do - create(:user, member_with_roles: { project => role }) - end - let(:role) { create(:project_role, permissions: []) } + let(:project) { create(:project) } + let(:user) { create(:user, member_with_permissions: { project => permissions }) } + let(:permissions) { [] } let (:url) { "file:///tmp/something/does/not/exist.svn" } let(:repository) do @@ -57,11 +51,16 @@ RSpec.describe RepositoriesController do before do login_as(user) + + visible_relation = double("relation").as_null_object + allow(Project).to receive(:visible).and_return(visible_relation) + allow(visible_relation).to receive(:find).and_return(project) + allow(project).to receive(:repository).and_return(repository) end describe "manages the repository" do - let(:role) { create(:project_role, permissions: [:manage_repository]) } + let(:permissions) { [:manage_repository] } before do # authorization checked in spec/permissions/manage_repositories_spec.rb @@ -107,7 +106,7 @@ RSpec.describe RepositoriesController do end describe "with empty repository" do - let(:role) { create(:project_role, permissions: [:browse_repository]) } + let(:permissions) { [:browse_repository] } before do allow(repository.scm) @@ -165,10 +164,7 @@ RSpec.describe RepositoriesController do end context "requested by an authorized user" do - let(:role) do - create(:project_role, permissions: %i[browse_repository - view_commit_author_statistics]) - end + let(:permissions) { %i[browse_repository view_commit_author_statistics] } it "is successful" do expect(response).to be_successful @@ -180,7 +176,7 @@ RSpec.describe RepositoriesController do end context "requested by an unauthorized user" do - let(:role) { create(:project_role, permissions: [:browse_repository]) } + let(:permissions) { [:browse_repository] } it "returns 403" do expect(response.code).to eq("403") @@ -189,7 +185,7 @@ RSpec.describe RepositoriesController do end describe "committers" do - let(:role) { create(:project_role, permissions: [:manage_repository]) } + let(:permissions) { [:manage_repository] } describe "#get" do before do @@ -222,10 +218,7 @@ RSpec.describe RepositoriesController do end describe "requested by a user with view_commit_author_statistics permission" do - let(:role) do - create(:project_role, permissions: %i[browse_repository - view_commit_author_statistics]) - end + let(:permissions) { %i[browse_repository view_commit_author_statistics] } it "show the commits per author graph" do expect(assigns(:show_commits_per_author)).to be(true) @@ -233,7 +226,7 @@ RSpec.describe RepositoriesController do end describe "requested by a user without view_commit_author_statistics permission" do - let(:role) { create(:project_role, permissions: [:browse_repository]) } + let(:permissions) { [:browse_repository] } it "does not show the commits per author graph" do expect(assigns(:show_commits_per_author)).to be(false) @@ -250,7 +243,7 @@ RSpec.describe RepositoriesController do describe "show" do render_views - let(:role) { create(:project_role, permissions: [:browse_repository]) } + let(:permissions) { [:browse_repository] } before do get :show, params: { project_id: project.identifier, repo_path: path } @@ -271,7 +264,7 @@ RSpec.describe RepositoriesController do describe "changes" do render_views - let(:role) { create(:project_role, permissions: [:browse_repository]) } + let(:permissions) { [:browse_repository] } before do get :changes, params: { project_id: project.identifier, repo_path: path } @@ -294,7 +287,7 @@ RSpec.describe RepositoriesController do describe "checkout path" do render_views - let(:role) { create(:project_role, permissions: [:browse_repository]) } + let(:permissions) { [:browse_repository] } let(:checkout_hash) do { "subversion" => { "enabled" => "1", From 4448b27f6acba1994799f0c98e1d5fc97c0c9a37 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 3 Feb 2026 11:04:15 +0100 Subject: [PATCH 195/293] Fix share controller specs --- app/components/shares/permission_button_component.rb | 2 +- spec/controllers/shares_controller_spec.rb | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/app/components/shares/permission_button_component.rb b/app/components/shares/permission_button_component.rb index 83f0c8162f7..a356df40a57 100644 --- a/app/components/shares/permission_button_component.rb +++ b/app/components/shares/permission_button_component.rb @@ -29,7 +29,7 @@ #++ module Shares - class PermissionButtonComponent < ApplicationComponent # rubocop:disable OpenProject/AddPreviewForViewComponent + class PermissionButtonComponent < ApplicationComponent include ApplicationHelper include OpPrimer::ComponentHelpers include OpTurbo::Streamable diff --git a/spec/controllers/shares_controller_spec.rb b/spec/controllers/shares_controller_spec.rb index f04ff8932ce..4a49b7fa208 100644 --- a/spec/controllers/shares_controller_spec.rb +++ b/spec/controllers/shares_controller_spec.rb @@ -309,15 +309,6 @@ RSpec.describe SharesController do expect(controller).to have_received(:respond_with_prepend_shares) end end - - context "when the user is locked" do - let(:shared_user) { new_locked_shared_user } - - it "calls respond_with_new_invite_form" do - make_request - expect(controller).to have_received(:respond_with_new_invite_form) - end - end end end From e9129ff1bcd32dbf79929a25bc7282865a295fe6 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 3 Feb 2026 11:06:12 +0100 Subject: [PATCH 196/293] Use an admin to test project settings instead of mocking all permissions --- spec/controllers/projects_settings_menu_controller_spec.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/controllers/projects_settings_menu_controller_spec.rb b/spec/controllers/projects_settings_menu_controller_spec.rb index 108a73a4b87..4154892b372 100644 --- a/spec/controllers/projects_settings_menu_controller_spec.rb +++ b/spec/controllers/projects_settings_menu_controller_spec.rb @@ -31,7 +31,7 @@ require "spec_helper" RSpec.describe Projects::Settings::ModulesController, "menu" do - let(:current_user) { build_stubbed(:user) } + let(:current_user) { create(:admin) } let(:project) do # project contains wiki by default @@ -41,7 +41,6 @@ RSpec.describe Projects::Settings::ModulesController, "menu" do let(:params) { { project_id: project.id } } before do - mock_permissions_for(current_user, &:allow_everything) login_as(current_user) end From 3e3c113c311d15d919d1aea5eb3fbf81c765c0a2 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 3 Feb 2026 11:08:40 +0100 Subject: [PATCH 197/293] Fix loading visible --- .../projects/settings/general_controller_spec.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/spec/controllers/projects/settings/general_controller_spec.rb b/spec/controllers/projects/settings/general_controller_spec.rb index 87bb63fab56..f780d8d369e 100644 --- a/spec/controllers/projects/settings/general_controller_spec.rb +++ b/spec/controllers/projects/settings/general_controller_spec.rb @@ -42,10 +42,9 @@ RSpec.describe Projects::Settings::GeneralController do let(:project) { build_stubbed(:project) } before do - allow(Project) - .to receive(:find) - .with(project.identifier) - .and_return(project) + visible_relation = instance_double(ActiveRecord::Relation) + allow(Project).to receive(:visible).and_return(visible_relation) + allow(visible_relation).to receive(:find).with(project.identifier).and_return(project) update_service = instance_double(Projects::UpdateService, call: service_result) From fa5153ef95de1f5501c185b6ae02e224547c3718 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 3 Feb 2026 11:11:13 +0100 Subject: [PATCH 198/293] fix watcher specs --- spec/requests/api/v3/watcher_resource_spec.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/spec/requests/api/v3/watcher_resource_spec.rb b/spec/requests/api/v3/watcher_resource_spec.rb index a2ac697653a..40e2261234b 100644 --- a/spec/requests/api/v3/watcher_resource_spec.rb +++ b/spec/requests/api/v3/watcher_resource_spec.rb @@ -171,9 +171,7 @@ RSpec.describe "API v3 Watcher resource", content_type: :json do context "when the target user is not allowed to watch the work package" do let(:new_watcher) { create(:user) } - it_behaves_like "constraint violation" do - let(:message) { "User is not allowed to view this resource." } - end + it_behaves_like "not found" end context "when the target user is locked" do From d58aea5feeb382a747a6ec6bc29857685867caf5 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 3 Feb 2026 11:14:40 +0100 Subject: [PATCH 199/293] fix workpackage api specs --- .../work_package_payload_representer_spec.rb | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/spec/lib/api/v3/work_packages/work_package_payload_representer_spec.rb b/spec/lib/api/v3/work_packages/work_package_payload_representer_spec.rb index 1e621317c2e..13a47b8b15e 100644 --- a/spec/lib/api/v3/work_packages/work_package_payload_representer_spec.rb +++ b/spec/lib/api/v3/work_packages/work_package_payload_representer_spec.rb @@ -675,12 +675,11 @@ RSpec.describe API::V3::WorkPackages::WorkPackagePayloadRepresenter do describe "parent" do let(:parent) { build_stubbed(:work_package) } let(:new_parent) do - wp = build_stubbed(:work_package) - allow(WorkPackage) - .to receive(:find_by) - .with(id: wp.id.to_s) - .and_return(wp) - wp + build_stubbed(:work_package).tap do |wp| + visible_relation = instance_double(ActiveRecord::Relation) + allow(WorkPackage).to receive(:visible).and_return(visible_relation) + allow(visible_relation).to receive(:find_by).with(id: wp.id.to_s).and_return(wp) + end end let(:path) { api_v3_paths.work_package(new_parent.id) } let(:links) do From 4c542dbc051910ea96a3180362e5af66499b44dc Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 3 Feb 2026 11:22:14 +0100 Subject: [PATCH 200/293] Fix tests for PIR PDF export --- .../project/pdf_export/project_initiation_spec.rb | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/spec/models/project/pdf_export/project_initiation_spec.rb b/spec/models/project/pdf_export/project_initiation_spec.rb index 23a1d4e0c46..6729441a705 100644 --- a/spec/models/project/pdf_export/project_initiation_spec.rb +++ b/spec/models/project/pdf_export/project_initiation_spec.rb @@ -40,12 +40,14 @@ RSpec.describe Project::PDFExport::ProjectInitiation do include_context "with a project with an arrangement of custom fields" let(:exporter) { described_class.new(project) } - let(:current_user) { create(:user, member_with_permissions: { project => %i[view_projects view_project_attributes] }) } + let(:current_user) do + create(:user, member_with_permissions: { project => %i[view_projects view_project_attributes view_work_packages] }) + end let(:export_time) { DateTime.new(2025, 11, 13, 13, 37) } let(:export_time_formatted) { format_time(export_time) } let(:wizard_status) { create(:status, name: "Submitted") } let(:status) { create(:status, name: "Approved") } - let(:work_package) { create(:work_package, status:) } + let(:work_package) { create(:work_package, project: project, status:) } let(:custom_artefact_name_key) { "project_mandate" } let(:section_a) { create(:project_custom_field_section, name: "Section A") } let(:section_b) { create(:project_custom_field_section, name: "Section B") } @@ -87,7 +89,6 @@ RSpec.describe Project::PDFExport::ProjectInitiation do expect(exporter.title).to eq("#{project.identifier}_#{exporter.sane_filename(custom_artefact_name)}_#{title_datetime}.pdf") end - it "exports a PDF containing project initiation using the custom defined name" do expected_document = [ custom_artefact_name, project.name, Setting.app_title, export_time_formatted, # cover page @@ -166,7 +167,12 @@ RSpec.describe Project::PDFExport::ProjectInitiation do end context "with a work package status" do - let(:project) { create(:project, project_creation_wizard_artifact_work_package_id: work_package.id) } + let(:project) { create(:project) } + + before do + # WorkPackage has to be created within the project so we cannot set it in the `create` call + project.update!(project_creation_wizard_artifact_work_package_id: work_package.id) + end it "uses a fixed pattern for the filename" do title_datetime = exporter.send(:title_datetime) From 516c42fcf73618de905d4685a2805766fb7171c7 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 3 Feb 2026 12:07:48 +0100 Subject: [PATCH 201/293] Correctly handle error cases for activity tab controller --- .../activities_tab_controller.rb | 35 +++++++------------ .../activities_tab_controller_spec.rb | 8 ++--- 2 files changed, 16 insertions(+), 27 deletions(-) diff --git a/app/controllers/work_packages/activities_tab_controller.rb b/app/controllers/work_packages/activities_tab_controller.rb index 0f642a64260..5d92a77a695 100644 --- a/app/controllers/work_packages/activities_tab_controller.rb +++ b/app/controllers/work_packages/activities_tab_controller.rb @@ -35,7 +35,6 @@ class WorkPackages::ActivitiesTabController < ApplicationController include WorkPackages::ActivitiesTab::StimulusControllers before_action :find_work_package - before_action :find_project before_action :find_journal, only: %i[emoji_actions item_actions edit cancel_edit update toggle_reaction] before_action :set_filter before_action :authorize @@ -201,43 +200,33 @@ class WorkPackages::ActivitiesTabController < ApplicationController private + def find_work_package + @work_package = WorkPackage.visible.find(params[:work_package_id]) + @project = @work_package.project + rescue ActiveRecord::RecordNotFound + respond_with_error(I18n.t("label_not_found")) + end + def initialize_pagination @paginator, @paginated_journals = WorkPackages::ActivitiesTab::Paginator .paginate(@work_package, params.merge(filter: @filter, limit: 20)) end def respond_with_error(error_message) - respond_to do |format| - # turbo_frame requests (tab is initially rendered and an error occured) are handled below + @turbo_status = :not_found + render_error_flash_message_via_turbo_stream(message: error_message) + + respond_to_with_turbo_streams do |format| format.html do render( - WorkPackages::ActivitiesTab::ErrorFrameComponent.new( - error_message: - ), + WorkPackages::ActivitiesTab::ErrorFrameComponent.new(error_message: error_message), layout: false, status: :not_found ) end - # turbo_stream requests (tab is already rendered and an error occured in subsequent requests) are handled below - format.turbo_stream do - @turbo_status = :not_found - render_error_flash_message_via_turbo_stream(message: error_message) - end end end - def find_work_package - @work_package = WorkPackage.visible.find(params[:work_package_id]) - rescue ActiveRecord::RecordNotFound - respond_with_error(I18n.t("label_not_found")) - end - - def find_project - @project = @work_package.project - rescue ActiveRecord::RecordNotFound - respond_with_error(I18n.t("label_not_found")) - end - def find_journal @journal = Journal .with_sequence_version diff --git a/spec/controllers/work_packages/activities_tab_controller_spec.rb b/spec/controllers/work_packages/activities_tab_controller_spec.rb index e43413fa486..a1e874ad355 100644 --- a/spec/controllers/work_packages/activities_tab_controller_spec.rb +++ b/spec/controllers/work_packages/activities_tab_controller_spec.rb @@ -217,7 +217,7 @@ RSpec.describe WorkPackages::ActivitiesTabController do subject { response } - it { is_expected.to be_unauthorized } + it { is_expected.to be_not_found } end end end @@ -228,7 +228,7 @@ RSpec.describe WorkPackages::ActivitiesTabController do subject { response } - it { is_expected.to be_forbidden } + it { is_expected.to be_not_found } end context "when a commenter is logged in who has no access to the project" do @@ -236,7 +236,7 @@ RSpec.describe WorkPackages::ActivitiesTabController do subject { response } - it { is_expected.to be_forbidden } + it { is_expected.to be_not_found } end context "when a user with full privileges is logged in who has no access to the project" do @@ -244,7 +244,7 @@ RSpec.describe WorkPackages::ActivitiesTabController do subject { response } - it { is_expected.to be_forbidden } + it { is_expected.to be_not_found } end end From d998dd3c256dbbeb28eb184c7dfefd9b18bc0669 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 3 Feb 2026 12:17:51 +0100 Subject: [PATCH 202/293] Fix more controller actions --- spec/controllers/groups_controller_spec.rb | 13 ++++++++++--- .../placeholder_users_controller_spec.rb | 6 +++++- spec/controllers/wiki_menu_authentication_spec.rb | 2 +- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index b9f0f649a02..dc7fabe5c3c 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -186,8 +186,8 @@ RSpec.describe GroupsController do it "shows" do get :show, params: { id: group.id } - expect(response).to be_successful - expect(response).to render_template "show" + expect(response).not_to be_successful + expect(response).to have_http_status :not_found end context "when having view_members permission in a project the group belongs to" do @@ -199,6 +199,11 @@ RSpec.describe GroupsController do create(:member, project:, principal: group, roles: [create(:project_role)]) end + it "shows" do + get :show, params: { id: group.id } + expect(response).to be_successful + end + it "shows members" do get :show, params: { id: group.id } expect(assigns(:group_users)).to match_array(group_members) @@ -217,7 +222,9 @@ RSpec.describe GroupsController do it "does not show members" do get :show, params: { id: group.id } - expect(assigns(:group_users)).to be_empty + + expect(response).to have_http_status :not_found + expect(assigns(:group_users)).to be_blank end end diff --git a/spec/controllers/placeholder_users_controller_spec.rb b/spec/controllers/placeholder_users_controller_spec.rb index 272b62817b8..5029e91daec 100644 --- a/spec/controllers/placeholder_users_controller_spec.rb +++ b/spec/controllers/placeholder_users_controller_spec.rb @@ -274,7 +274,11 @@ RSpec.describe PlaceholderUsersController do end describe "GET show" do - it_behaves_like "renders the show template" + before do + get :show, params: { id: placeholder_user.id } + end + + it { expect(response).to have_http_status :not_found } end describe "GET edit" do diff --git a/spec/controllers/wiki_menu_authentication_spec.rb b/spec/controllers/wiki_menu_authentication_spec.rb index a318fc0576b..33e827bbb1d 100644 --- a/spec/controllers/wiki_menu_authentication_spec.rb +++ b/spec/controllers/wiki_menu_authentication_spec.rb @@ -64,7 +64,7 @@ RSpec.describe WikiMenuItemsController do get "edit", params: @params - expect(response).to have_http_status(:forbidden) + expect(response).to have_http_status(:not_found) end end end From 466a6ac81b53e6d719253508e28da56386f95f97 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 3 Feb 2026 12:36:17 +0100 Subject: [PATCH 203/293] Fox more specs and controllers --- .../members/role_form_component.html.erb | 2 +- .../hierarchy/items_base_controller.rb | 6 ++++-- .../hierarchy/items_controller.rb | 2 +- .../app/controllers/documents_controller.rb | 1 + .../controllers/documents_controller_spec.rb | 19 +++++------------- .../grids/overviews_controller_spec.rb | 20 ++++--------------- .../admin/health_status_controller.rb | 2 +- .../admin/health_status_controller_spec.rb | 4 +++- .../two_factor_devices_controller_spec.rb | 2 +- 9 files changed, 21 insertions(+), 37 deletions(-) diff --git a/app/components/members/role_form_component.html.erb b/app/components/members/role_form_component.html.erb index 01567d6f650..dd6a73754f7 100644 --- a/app/components/members/role_form_component.html.erb +++ b/app/components/members/role_form_component.html.erb @@ -27,7 +27,7 @@ See COPYRIGHT and LICENSE files for more details. ++#%> -<%= form_with model: member.new_record? ? [member.project, member] : member, +<%= form_with model: [member.project, member], builder: TabularFormBuilder, html: form_html_options do |f| %> <%= hidden_field_tag :page, params[:page].to_i %> diff --git a/app/controllers/admin/custom_fields/hierarchy/items_base_controller.rb b/app/controllers/admin/custom_fields/hierarchy/items_base_controller.rb index 41cb30747d5..87323c15a7b 100644 --- a/app/controllers/admin/custom_fields/hierarchy/items_base_controller.rb +++ b/app/controllers/admin/custom_fields/hierarchy/items_base_controller.rb @@ -37,7 +37,9 @@ module Admin layout :admin_or_frame_layout - before_action :require_admin, :find_custom_field, :find_active_item + before_action :require_admin + before_action :find_custom_field + before_action :find_active_item # See https://github.com/hotwired/turbo-rails?tab=readme-ov-file#a-note-on-custom-layouts def admin_or_frame_layout @@ -232,7 +234,7 @@ module Admin @active_item = if params[:id].present? CustomField::Hierarchy::Item.including_children.find(params[:id]) else - @object.hierarchy_root + @custom_field.hierarchy_root end end end diff --git a/app/controllers/admin/custom_fields/hierarchy/items_controller.rb b/app/controllers/admin/custom_fields/hierarchy/items_controller.rb index a4816eac0c5..0cc80efdf09 100644 --- a/app/controllers/admin/custom_fields/hierarchy/items_controller.rb +++ b/app/controllers/admin/custom_fields/hierarchy/items_controller.rb @@ -37,7 +37,7 @@ module Admin private def find_custom_field - CustomField.hierarchy_root_and_children.find(params[:custom_field_id]) + @custom_field = CustomField.hierarchy_root_and_children.find(params[:custom_field_id]) end end end diff --git a/modules/documents/app/controllers/documents_controller.rb b/modules/documents/app/controllers/documents_controller.rb index cac49b807c3..91d181434e0 100644 --- a/modules/documents/app/controllers/documents_controller.rb +++ b/modules/documents/app/controllers/documents_controller.rb @@ -37,6 +37,7 @@ class DocumentsController < ApplicationController default_search_scope :documents before_action :find_project_by_project_id, only: %i[index search new create] + before_action :find_document, except: %i[index search new create] before_action :authorize def index diff --git a/modules/documents/spec/controllers/documents_controller_spec.rb b/modules/documents/spec/controllers/documents_controller_spec.rb index 955eb40e138..c34efec1872 100644 --- a/modules/documents/spec/controllers/documents_controller_spec.rb +++ b/modules/documents/spec/controllers/documents_controller_spec.rb @@ -35,8 +35,7 @@ RSpec.describe DocumentsController do let(:admin) { create(:admin) } let(:project) { create(:project, name: "Test Project") } - let(:user) { create(:user) } - let(:role) { create(:project_role, permissions: [:view_documents]) } + let(:user) { create(:user, member_with_permissions: { project => [:view_documents] }) } let(:document_type) do create(:document_type, name: "Default Type") @@ -129,15 +128,12 @@ RSpec.describe DocumentsController do let(:uncontainered) { create(:attachment, container: nil, author: admin) } before do - notify_project = project - create(:member, project: notify_project, user:, roles: [role]) - post :create, params: { - project_id: notify_project.identifier, + project_id: project.identifier, document: attributes_for(:document, title: "New Document", - project_id: notify_project.id, + project_id: project.id, type_id: document_type.id, kind: "classic"), attachments: { "1" => { id: uncontainered.id } } @@ -191,15 +187,10 @@ RSpec.describe DocumentsController do collaborative_editing_hocuspocus_url: "wss://hocuspocus.local", collaborative_editing_hocuspocus_secret: "secret1234" } do - let(:manage_role) { create(:project_role, permissions: %i[view_documents manage_documents]) } - let(:view_only_role) { create(:project_role, permissions: [:view_documents]) } - let(:user_with_manage) { create(:user) } - let(:user_without_manage) { create(:user) } + let(:user_with_manage) { create(:user, member_with_permissions: { project => %i[view_documents manage_documents] }) } + let(:user_without_manage) { create(:user, member_with_permissions: { project => [:view_documents] }) } before do - create(:member, project:, user: user_with_manage, roles: [manage_role]) - create(:member, project:, user: user_without_manage, roles: [view_only_role]) - document.update(kind: :collaborative) end diff --git a/modules/grids/spec/controllers/grids/overviews_controller_spec.rb b/modules/grids/spec/controllers/grids/overviews_controller_spec.rb index 0ec9fde690e..851d1528854 100644 --- a/modules/grids/spec/controllers/grids/overviews_controller_spec.rb +++ b/modules/grids/spec/controllers/grids/overviews_controller_spec.rb @@ -29,28 +29,16 @@ require "spec_helper" RSpec.describe Overviews::OverviewsController do - let(:permissions) do - %i(view_project) - end - let(:project) do - build_stubbed(:project).tap do |p| - allow(Project) - .to receive(:find) - .with(p.id.to_s) - .and_return(p) - end - end + let(:permissions) { %i[view_project] } + let(:project) { create(:project) } + let(:main_app_routes) do Rails.application.routes.url_helpers end - let(:current_user) { build_stubbed(:user) } + let(:current_user) { create(:user, member_with_permissions: { project => permissions }) } before do - mock_permissions_for(current_user) do |mock| - mock.allow_in_project *permissions, :view_news, :manage_dashboards, project: - end - login_as current_user end diff --git a/modules/storages/app/controllers/storages/admin/health_status_controller.rb b/modules/storages/app/controllers/storages/admin/health_status_controller.rb index 43b2f34c8e1..47c38968e66 100644 --- a/modules/storages/app/controllers/storages/admin/health_status_controller.rb +++ b/modules/storages/app/controllers/storages/admin/health_status_controller.rb @@ -82,7 +82,7 @@ module Storages end def find_storage - @storage = Storages::Storage.find(params[:storage_id]) + @storage = ::Storages::Storage.visible.find(params[:storage_id]) end def create_and_cache_report diff --git a/modules/storages/spec/controllers/storages/admin/health_status_controller_spec.rb b/modules/storages/spec/controllers/storages/admin/health_status_controller_spec.rb index 08fd7264700..0179578ea8d 100644 --- a/modules/storages/spec/controllers/storages/admin/health_status_controller_spec.rb +++ b/modules/storages/spec/controllers/storages/admin/health_status_controller_spec.rb @@ -36,7 +36,9 @@ RSpec.describe Storages::Admin::HealthStatusController do let(:params) { { storage_id: storage.id } } before do - allow(Storages::Storage).to receive(:find).with(storage.id.to_s).and_return(storage) + visible_relation = instance_double(ActiveRecord::Relation) + allow(Storages::Storage).to receive(:visible).and_return(visible_relation) + allow(visible_relation).to receive(:find).with(storage.id.to_s).and_return(storage) login_as user end diff --git a/modules/two_factor_authentication/spec/controllers/two_factor_authentication/users/two_factor_devices_controller_spec.rb b/modules/two_factor_authentication/spec/controllers/two_factor_authentication/users/two_factor_devices_controller_spec.rb index 831b1d3ba95..50a4c987352 100644 --- a/modules/two_factor_authentication/spec/controllers/two_factor_authentication/users/two_factor_devices_controller_spec.rb +++ b/modules/two_factor_authentication/spec/controllers/two_factor_authentication/users/two_factor_devices_controller_spec.rb @@ -37,7 +37,7 @@ RSpec.describe TwoFactorAuthentication::Users::TwoFactorDevicesController do let(:logged_in_user) { other_user } it "does not give access" do - expect(response).to have_http_status :forbidden + expect(response).to have_http_status :not_found end end From 2c96fa84d33db929fd562c6010f168b946c828e1 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 3 Feb 2026 15:57:55 +0100 Subject: [PATCH 204/293] Fix specs for costlog controller --- .../app/controllers/costlog_controller.rb | 8 +- .../controllers/costlog_controller_spec.rb | 150 +++++++++++------- 2 files changed, 99 insertions(+), 59 deletions(-) diff --git a/modules/costs/app/controllers/costlog_controller.rb b/modules/costs/app/controllers/costlog_controller.rb index 79904e0f8e4..611720e2336 100644 --- a/modules/costs/app/controllers/costlog_controller.rb +++ b/modules/costs/app/controllers/costlog_controller.rb @@ -30,7 +30,7 @@ class CostlogController < ApplicationController menu_item :work_packages - before_action :find_project, :authorize, only: %i[edit new create update destroy] + before_action :find_cost_entry_work_package_or_project, :authorize, only: %i[edit new create update destroy] before_action :find_associated_objects, only: %i[create update] helper :work_packages @@ -96,8 +96,7 @@ class CostlogController < ApplicationController private - def find_project - # copied from timelog_controller.rb + def find_cost_entry_work_package_or_project # rubocop:disable Metrics/AbcSize if params[:id] @cost_entry = CostEntry.visible.find(params[:id]) @project = @cost_entry.project @@ -108,11 +107,10 @@ class CostlogController < ApplicationController @project = Project.visible.find(params[:project_id]) else render_404 - false end end - def find_associated_objects + def find_associated_objects # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity user_id = cost_entry_params.delete(:user_id) @user = if @cost_entry.present? && @cost_entry.user_id == user_id @cost_entry.user diff --git a/modules/costs/spec/controllers/costlog_controller_spec.rb b/modules/costs/spec/controllers/costlog_controller_spec.rb index d9f853361ec..cb62c508057 100644 --- a/modules/costs/spec/controllers/costlog_controller_spec.rb +++ b/modules/costs/spec/controllers/costlog_controller_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH @@ -30,6 +32,7 @@ require_relative "../spec_helper" RSpec.describe CostlogController do include Cost::PluginSpecHelper + let (:project) { create(:project_with_types) } let (:work_package) do create(:work_package, project:, @@ -68,7 +71,7 @@ RSpec.describe CostlogController do shared_examples_for "assigns" do it do expect(assigns(:cost_entry).project).to eq(expected_project) - expect(assigns(:cost_entry).entity).to eq(expected_work_package) + expect(assigns(:cost_entry).entity).to eq(expected_entity) expect(assigns(:cost_entry).user).to eq(expected_user) expect(assigns(:cost_entry).spent_on).to eq(expected_spent_on) expect(assigns(:cost_entry).cost_type).to eq(expected_cost_type) @@ -94,7 +97,7 @@ RSpec.describe CostlogController do end let(:expected_project) { project } - let(:expected_work_package) { work_package } + let(:expected_entity) { work_package } let(:expected_user) { user } let(:expected_spent_on) { Date.current } let(:expected_cost_type) { nil } @@ -112,17 +115,25 @@ RSpec.describe CostlogController do it { expect(response).to render_template("edit") } end + shared_examples_for "not_found new" do + before do + get :new, params: + end + + it { expect(response).to have_http_status(:not_found) } + end + shared_examples_for "forbidden new" do before do get :new, params: end - it { expect(response.response_code).to eq(403) } + it { expect(response).to have_http_status(:forbidden) } end describe "WHEN user allowed to create new cost_entry" do before do - grant_current_user_permissions user, [:log_costs] + grant_current_user_permissions user, %i[view_project view_work_packages log_costs] end it_behaves_like "successful new" @@ -136,7 +147,7 @@ RSpec.describe CostlogController do cost_type.default = true cost_type.save! - grant_current_user_permissions user, [:log_costs] + grant_current_user_permissions user, %i[view_project view_work_packages log_costs] end it_behaves_like "successful new" @@ -144,7 +155,7 @@ RSpec.describe CostlogController do describe "WHEN user is allowed to create new own cost_entry" do before do - grant_current_user_permissions user, [:log_own_costs] + grant_current_user_permissions user, %i[view_project view_work_packages log_own_costs] end it_behaves_like "successful new" @@ -152,11 +163,15 @@ RSpec.describe CostlogController do describe "WHEN user is not allowed to create new cost_entries" do before do - grant_current_user_permissions user, [] + grant_current_user_permissions user, %i[view_project view_work_packages] end it_behaves_like "forbidden new" end + + describe "WHEN user is not a project member" do + it_behaves_like "not_found new" + end end describe "GET edit" do @@ -177,17 +192,25 @@ RSpec.describe CostlogController do it { expect(response).to render_template("edit") } end + shared_examples_for "not_found edit" do + before do + get :edit, params: + end + + it { expect(response).to have_http_status(:not_found) } + end + shared_examples_for "forbidden edit" do before do get :edit, params: end - it { expect(response.response_code).to eq(403) } + it { expect(response).to have_http_status(:forbidden) } end describe "WHEN the user is allowed to edit cost_entries" do before do - grant_current_user_permissions user, [:edit_cost_entries] + grant_current_user_permissions user, %i[view_project view_work_packages view_cost_entries edit_cost_entries] end it_behaves_like "successful edit" @@ -196,7 +219,7 @@ RSpec.describe CostlogController do describe "WHEN the user is allowed to edit cost_entries " \ "WHEN trying to edit a not own cost_entry" do before do - grant_current_user_permissions user, [:edit_cost_entries] + grant_current_user_permissions user, %i[view_project view_work_packages view_cost_entries edit_cost_entries] cost_entry.user = create(:user) cost_entry.save(validate: false) @@ -207,7 +230,7 @@ RSpec.describe CostlogController do describe "WHEN the user is allowed to edit own cost_entries" do before do - grant_current_user_permissions user, [:edit_own_cost_entries] + grant_current_user_permissions user, %i[view_project view_work_packages view_cost_entries edit_own_cost_entries] end it_behaves_like "successful edit" @@ -216,7 +239,7 @@ RSpec.describe CostlogController do describe "WHEN the user is allowed to edit own cost_entries " \ "WHEN trying to edit a not own cost_entry" do before do - grant_current_user_permissions user, [:edit_own_cost_entries] + grant_current_user_permissions user, %i[view_project view_work_packages view_cost_entries edit_own_cost_entries] cost_entry.user = create(:user) cost_entry.save(validate: false) @@ -227,7 +250,7 @@ RSpec.describe CostlogController do describe "WHEN the user is not allowed to edit cost_entries" do before do - grant_current_user_permissions user, [] + grant_current_user_permissions user, %i[view_project view_work_packages view_cost_entries] end it_behaves_like "forbidden edit" @@ -236,7 +259,7 @@ RSpec.describe CostlogController do describe "WHEN the user is allowed to edit cost_entries " \ "WHEN the cost_entry is associated to a different project" do before do - grant_current_user_permissions user, [:edit_cost_entries] + grant_current_user_permissions user, %i[view_project view_work_packages view_cost_entries edit_cost_entries] cost_entry.project = create(:project_with_types) cost_entry.entity = create(:work_package, project: cost_entry.project, @@ -245,13 +268,13 @@ RSpec.describe CostlogController do cost_entry.save! end - it_behaves_like "forbidden edit" + it_behaves_like "not_found edit" end describe "WHEN the user is allowed to edit cost_entries " \ "WHEN the provided id is invalid" do before do - grant_current_user_permissions user, [:edit_cost_entries] + grant_current_user_permissions user, %i[view_project view_work_packages edit_cost_entries] params["id"] = "this-entry-does-not-exist" @@ -275,7 +298,7 @@ RSpec.describe CostlogController do "overridden_costs" => overridden_costs.to_s } } end let(:expected_project) { project } - let(:expected_work_package) { work_package } + let(:expected_entity) { work_package } let(:expected_user) { user } let(:expected_overridden_costs) { overridden_costs } let(:expected_spent_on) { date } @@ -288,7 +311,7 @@ RSpec.describe CostlogController do let(:units) { 5.0 } before do - cost_type.save! if cost_type.present? + cost_type.presence&.save! end shared_examples_for "successful create" do @@ -319,12 +342,20 @@ RSpec.describe CostlogController do post :create, params: end - it { expect(response.response_code).to eq(403) } + it { expect(response).to have_http_status(:forbidden) } + end + + shared_examples_for "not_found create" do + before do + post :create, params: + end + + it { expect(response).to have_http_status(:not_found) } end describe "WHEN the user is allowed to create cost_entries" do before do - grant_current_user_permissions user, [:log_costs] + grant_current_user_permissions user, %i[view_project view_work_packages log_costs] end it_behaves_like "successful create" @@ -332,7 +363,7 @@ RSpec.describe CostlogController do describe "WHEN the user is allowed to create own cost_entries" do before do - grant_current_user_permissions user, [:log_own_costs] + grant_current_user_permissions user, %i[view_project view_work_packages log_own_costs] end it_behaves_like "successful create" @@ -343,7 +374,7 @@ RSpec.describe CostlogController do let(:expected_spent_on) { Date.today } before do - grant_current_user_permissions user, [:log_costs] + grant_current_user_permissions user, %i[view_project view_work_packages log_costs] params["cost_entry"].delete("spent_on") end @@ -357,7 +388,7 @@ RSpec.describe CostlogController do let(:expected_cost_type) { nil } before do - grant_current_user_permissions user, [:log_costs] + grant_current_user_permissions user, %i[view_project view_work_packages log_costs] params["cost_entry"]["cost_type_id"] = (cost_type.id + 1).to_s end @@ -372,7 +403,7 @@ RSpec.describe CostlogController do before do create(:cost_type, default: true) - grant_current_user_permissions user, [:log_costs] + grant_current_user_permissions user, %i[view_project view_work_packages log_costs] params["cost_entry"]["cost_type_id"] = 1 end @@ -387,7 +418,7 @@ RSpec.describe CostlogController do before do create(:cost_type, default: true) - grant_current_user_permissions user, [:log_costs] + grant_current_user_permissions user, %i[view_project view_work_packages log_costs] params["cost_entry"].delete("cost_type_id") end @@ -400,7 +431,7 @@ RSpec.describe CostlogController do let(:expected_cost_type) { nil } before do - grant_current_user_permissions user, [:log_costs] + grant_current_user_permissions user, %i[view_project view_work_packages log_costs] params["cost_entry"].delete("cost_type_id") end @@ -410,7 +441,7 @@ RSpec.describe CostlogController do describe "WHEN the user is allowed to create cost_entries " \ "WHEN the cost_type id provided belongs to an inactive cost_type" do before do - grant_current_user_permissions user, [:log_costs] + grant_current_user_permissions user, %i[view_project view_work_packages log_costs] cost_type.deleted_at = Date.today cost_type.save! end @@ -422,8 +453,8 @@ RSpec.describe CostlogController do "WHEN the user is allowed to log cost for someone else and is doing so " \ "WHEN the other user is a member of the project" do before do - grant_current_user_permissions user, [] - grant_current_user_permissions user2, [:log_costs] + grant_current_user_permissions user, %i[view_project view_work_packages] + grant_current_user_permissions user2, %i[view_project view_work_packages log_costs] params["cost_entry"]["user_id"] = user.id.to_s end @@ -435,11 +466,13 @@ RSpec.describe CostlogController do "WHEN the user is allowed to log cost for someone else and is doing so " \ "WHEN the other user isn't a member of the project" do before do - grant_current_user_permissions user2, [:log_costs] + grant_current_user_permissions user2, %i[view_project view_work_packages log_costs] params["cost_entry"]["user_id"] = user.id.to_s end + let(:expected_user) { nil } # user isn't member so won't be found + it_behaves_like "invalid create" end @@ -451,10 +484,10 @@ RSpec.describe CostlogController do type: project2.types.first, author: user) end - let(:expected_work_package) { work_package2 } + let(:expected_entity) { nil } # user has no access to the WP so it won't be found before do - grant_current_user_permissions user, [:log_costs] + grant_current_user_permissions user, %i[view_project view_work_packages log_costs] params["cost_entry"]["entity_id"] = work_package2.id end @@ -464,10 +497,10 @@ RSpec.describe CostlogController do describe "WHEN the user is allowed to create cost_entries " \ "WHEN no work_package_id is provided" do - let(:expected_work_package) { nil } + let(:expected_entity) { nil } before do - grant_current_user_permissions user, [:log_costs] + grant_current_user_permissions user, %i[view_project view_work_packages log_costs] params["cost_entry"].delete("entity_id") end @@ -478,7 +511,7 @@ RSpec.describe CostlogController do describe "WHEN the user is allowed to create own cost_entries " \ "WHEN the user is trying to log costs for somebody else" do before do - grant_current_user_permissions user2, [:log_own_costs] + grant_current_user_permissions user2, %i[view_project view_work_packages log_own_costs] params["cost_entry"]["user_id"] = user.id end @@ -488,7 +521,7 @@ RSpec.describe CostlogController do describe "WHEN the user is not allowed to create cost_entries" do before do - grant_current_user_permissions user, [] + grant_current_user_permissions user, %i[view_project view_work_packages] end it_behaves_like "forbidden create" @@ -500,13 +533,13 @@ RSpec.describe CostlogController do { "id" => cost_entry.id.to_s, "cost_entry" => { "comments" => "lorem", "entity_type" => "WorkPackage", - "entity_id" => cost_entry.work_package.id.to_s, + "entity_id" => cost_entry.entity_id.to_s, "units" => cost_entry.units.to_s, "spent_on" => cost_entry.spent_on.to_s, "user_id" => cost_entry.user.id.to_s, "cost_type_id" => cost_entry.cost_type.id.to_s } } end - let(:expected_work_package) { cost_entry.work_package } + let(:expected_entity) { cost_entry.entity } let(:expected_user) { cost_entry.user } let(:expected_project) { cost_entry.project } let(:expected_cost_type) { cost_entry.cost_type } @@ -539,6 +572,7 @@ RSpec.describe CostlogController do it_behaves_like "assigns" it { expect(response).to be_successful } it { expect(flash[:notice]).to be_nil } + it { expect(response).to render_template("edit") } end shared_examples_for "forbidden update" do @@ -546,7 +580,15 @@ RSpec.describe CostlogController do put :update, params: end - it { expect(response.response_code).to eq(403) } + it { expect(response).to have_http_status(:forbidden) } + end + + shared_examples_for "not_found update" do + before do + put :update, params: + end + + it { expect(response).to have_http_status(:not_found) } end describe "WHEN the user is allowed to update cost_entries " \ @@ -558,7 +600,7 @@ RSpec.describe CostlogController do cost_type overridden_costs spent_on" do - let(:expected_work_package) do + let(:expected_entity) do create(:work_package, project:, type: project.types.first, author: user) @@ -571,10 +613,10 @@ RSpec.describe CostlogController do before do grant_current_user_permissions expected_user, [] - grant_current_user_permissions user, [:edit_cost_entries] + grant_current_user_permissions user, %i[view_project view_work_packages view_cost_entries edit_cost_entries] params["cost_entry"]["entity_type"] = "WorkPackage" - params["cost_entry"]["entity_id"] = expected_work_package.id.to_s + params["cost_entry"]["entity_id"] = expected_entity.id.to_s params["cost_entry"]["user_id"] = expected_user.id.to_s params["cost_entry"]["spent_on"] = expected_spent_on.to_s params["cost_entry"]["units"] = expected_units.to_s @@ -588,7 +630,7 @@ RSpec.describe CostlogController do describe "WHEN the user is allowed to update cost_entries " \ "WHEN updating nothing" do before do - grant_current_user_permissions user, [:edit_cost_entries] + grant_current_user_permissions user, %i[view_project view_work_packages view_cost_entries edit_cost_entries] end it_behaves_like "successful update" @@ -599,7 +641,7 @@ RSpec.describe CostlogController do let(:expected_units) { cost_entry.units + 20 } before do - grant_current_user_permissions user, [:edit_own_cost_entries] + grant_current_user_permissions user, %i[view_project view_work_packages view_cost_entries edit_own_cost_entries] params["cost_entry"]["units"] = expected_units.to_s end @@ -611,10 +653,10 @@ RSpec.describe CostlogController do "WHEN updating the user " \ "WHEN the new user isn't a member of the project" do let(:user2) { create(:user) } - let(:expected_user) { user2 } + let(:expected_user) { nil } # user is not allowed to see the user so we cannot expect it to be assigned before do - grant_current_user_permissions user, [:edit_cost_entries] + grant_current_user_permissions user, %i[view_project view_work_packages view_cost_entries edit_cost_entries] params["cost_entry"]["user_id"] = user2.id.to_s end @@ -630,10 +672,10 @@ RSpec.describe CostlogController do create(:work_package, project: project2, type: project2.types.first) end - let(:expected_work_package) { work_package2 } + let(:expected_entity) { nil } # user has no access to the WP so it won't be found before do - grant_current_user_permissions user, [:edit_cost_entries] + grant_current_user_permissions user, %i[view_project view_work_packages view_cost_entries edit_cost_entries] params["cost_entry"]["entity_id"] = work_package2.id.to_s end @@ -644,10 +686,10 @@ RSpec.describe CostlogController do describe "WHEN the user is allowed to update cost_entries " \ "WHEN updating the entity " \ "WHEN the new entity isn't existing" do - let(:expected_work_package) { nil } + let(:expected_entity) { nil } before do - grant_current_user_permissions user, [:edit_cost_entries] + grant_current_user_permissions user, %i[view_project view_work_packages view_cost_entries edit_cost_entries] params["cost_entry"]["entity_id"] = "this-id-does-not-exist" end @@ -661,7 +703,7 @@ RSpec.describe CostlogController do let(:expected_cost_type) { create(:cost_type, deleted_at: Date.today) } before do - grant_current_user_permissions user, [:edit_cost_entries] + grant_current_user_permissions user, %i[view_project view_work_packages view_cost_entries edit_cost_entries] params["cost_entry"]["cost_type_id"] = expected_cost_type.id.to_s end @@ -675,7 +717,7 @@ RSpec.describe CostlogController do let(:expected_cost_type) { nil } before do - grant_current_user_permissions user, [:edit_cost_entries] + grant_current_user_permissions user, %i[view_project view_work_packages view_cost_entries edit_cost_entries] params["cost_entry"]["cost_type_id"] = "1234123512" end @@ -689,7 +731,7 @@ RSpec.describe CostlogController do let(:user3) { create(:user) } before do - grant_current_user_permissions user, [:edit_own_cost_entries] + grant_current_user_permissions user, %i[view_project view_work_packages view_cost_entries edit_own_cost_entries] params["cost_entry"]["user_id"] = user3.id end @@ -703,7 +745,7 @@ RSpec.describe CostlogController do let(:user3) { create(:user) } before do - grant_current_user_permissions user3, [:edit_own_cost_entries] + grant_current_user_permissions user3, %i[view_project view_work_packages view_cost_entries edit_own_cost_entries] params["cost_entry"]["units"] = (cost_entry.units + 20).to_s end From 59255a2e3ce1264960f2631698b62a3ffda67118 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 3 Feb 2026 16:05:52 +0100 Subject: [PATCH 205/293] Fix routing specs for forums and messages --- spec/routing/messages_spec.rb | 62 +++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/spec/routing/messages_spec.rb b/spec/routing/messages_spec.rb index 7c0989512d4..7ae59d4dd8b 100644 --- a/spec/routing/messages_spec.rb +++ b/spec/routing/messages_spec.rb @@ -33,51 +33,65 @@ require "spec_helper" RSpec.describe MessagesController, "routing" do context "project scoped" do it { - expect(subject).to route(:get, "/forums/lala/topics/new").to(controller: "messages", - action: "new", - forum_id: "lala") + expect(subject).to route(:get, "/projects/some-project/forums/lala/topics/new").to(controller: "messages", + action: "new", + project_id: "some-project", + forum_id: "lala") } it { - expect(subject).to route(:post, "/forums/lala/topics").to(controller: "messages", - action: "create", - forum_id: "lala") + expect(subject).to route(:post, "/projects/some-project/forums/lala/topics").to(controller: "messages", + action: "create", + project_id: "some-project", + forum_id: "lala") } end it { - expect(subject).to route(:get, "/topics/2").to(controller: "messages", - action: "show", - id: "2") + expect(subject).to route(:get, "/projects/some-project/forums/lala/topics/2").to(controller: "messages", + action: "show", + project_id: "some-project", + forum_id: "lala", + id: "2") } it { - expect(subject).to route(:get, "/topics/22/edit").to(controller: "messages", - action: "edit", - id: "22") + expect(subject).to route(:get, "/projects/some-project/forums/lala/topics/22/edit").to(controller: "messages", + action: "edit", + project_id: "some-project", + forum_id: "lala", + id: "22") } it { - expect(subject).to route(:put, "/topics/22").to(controller: "messages", - action: "update", - id: "22") + expect(subject).to route(:put, "/projects/some-project/forums/lala/topics/22").to(controller: "messages", + action: "update", + project_id: "some-project", + forum_id: "lala", + id: "22") } it { - expect(subject).to route(:delete, "/topics/555").to(controller: "messages", - action: "destroy", - id: "555") + expect(subject).to route(:delete, "/projects/some-project/forums/lala/topics/555").to(controller: "messages", + action: "destroy", + project_id: "some-project", + forum_id: "lala", + id: "555") } it { - expect(subject).to route(:get, "/topics/22/quote").to(controller: "messages", - action: "quote", - id: "22") + expect(subject).to route(:get, "/projects/some-project/forums/lala/topics/22/quote").to(controller: "messages", + action: "quote", + project_id: "some-project", + forum_id: "lala", + id: "22") } it { - expect(subject).to route(:post, "/topics/555/reply").to(controller: "messages", - action: "reply", - id: "555") + expect(subject).to route(:post, "/projects/some-project/forums/lala/topics/555/reply").to(controller: "messages", + action: "reply", + project_id: "some-project", + forum_id: "lala", + id: "555") } end From 361e8461d2e34f47540689d5202247f857182875 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 3 Feb 2026 16:18:55 +0100 Subject: [PATCH 206/293] fix problems uncovered in routing specs --- app/views/user_mailer/news_added.html.erb | 2 +- app/views/user_mailer/news_added.text.erb | 2 +- .../grids/widgets/news/item.html.erb | 2 +- .../components/grids/widgets/news_spec.rb | 4 +- spec/lib/open_project/static_routing_spec.rb | 4 +- spec/requests/news/comments_destroy_spec.rb | 4 +- spec/routing/messages_spec.rb | 34 +++--- spec/routing/news_comments_spec.rb | 23 ++-- spec/routing/news_spec.rb | 115 +++++++++--------- 9 files changed, 98 insertions(+), 92 deletions(-) diff --git a/app/views/user_mailer/news_added.html.erb b/app/views/user_mailer/news_added.html.erb index 28ef3745901..3062fbe6750 100644 --- a/app/views/user_mailer/news_added.html.erb +++ b/app/views/user_mailer/news_added.html.erb @@ -27,7 +27,7 @@ See COPYRIGHT and LICENSE files for more details. ++#%> -

    <%= link_to @news.title, news_url(@news) %>

    +

    <%= link_to @news.title, project_news_url(@news.project, @news) %>

    <%= @news.author&.name %> <%= format_text @news.description, only_path: false %> diff --git a/app/views/user_mailer/news_added.text.erb b/app/views/user_mailer/news_added.text.erb index 90701cd335e..d9ff46760f5 100644 --- a/app/views/user_mailer/news_added.text.erb +++ b/app/views/user_mailer/news_added.text.erb @@ -28,7 +28,7 @@ See COPYRIGHT and LICENSE files for more details. ++#%> <%= @news.title %> -<%= news_url(@news) %> +<%= project_news_url(@news.project, @news) %> <%= @news.author&.name %> <%= @news.description %> diff --git a/modules/grids/app/components/grids/widgets/news/item.html.erb b/modules/grids/app/components/grids/widgets/news/item.html.erb index 5ef5a51cd40..343959c4dda 100644 --- a/modules/grids/app/components/grids/widgets/news/item.html.erb +++ b/modules/grids/app/components/grids/widgets/news/item.html.erb @@ -46,7 +46,7 @@ See COPYRIGHT and LICENSE files for more details. end layout.with_column do - render(Primer::Beta::Link.new(font_weight: :bold, href: news_path(item))) { item.title } + render(Primer::Beta::Link.new(font_weight: :bold, href: project_news_path(item.project, item))) { item.title } end end ) diff --git a/modules/grids/spec/components/grids/widgets/news_spec.rb b/modules/grids/spec/components/grids/widgets/news_spec.rb index 0be4c7326b0..31c9b61859c 100644 --- a/modules/grids/spec/components/grids/widgets/news_spec.rb +++ b/modules/grids/spec/components/grids/widgets/news_spec.rb @@ -82,7 +82,7 @@ RSpec.describe Grids::Widgets::News, type: :component do it "renders news items from all projects", :aggregate_failures do expect(rendered_component).to have_list_item(count: 2) expect(rendered_component).to have_list_item(position: 2) do |item| - expect(item).to have_link href: news_path(news_red) + expect(item).to have_link href: project_news_path(project_red, news_red) expect(item).to have_content(/Added by .+ on \d{2}\/\d{2}\/\d{4} \d{2}:\d{2} [AP]M/) expect(item).to have_link href: user_path(author) end @@ -105,7 +105,7 @@ RSpec.describe Grids::Widgets::News, type: :component do it "renders only this project’s news" do expect(rendered_component).to have_list_item(count: 3) expect(rendered_component).to have_list_item(position: 3) do |item| - expect(item).to have_link href: news_path(news_items.first) + expect(item).to have_link href: project_news_path(project, news_items.first) expect(item).to have_content(/Added by .+ on \d{2}\/\d{2}\/\d{4} \d{2}:\d{2} [AP]M/) expect(item).to have_link href: user_path(author) end diff --git a/spec/lib/open_project/static_routing_spec.rb b/spec/lib/open_project/static_routing_spec.rb index d0151d64c1f..a1af0aa2dde 100644 --- a/spec/lib/open_project/static_routing_spec.rb +++ b/spec/lib/open_project/static_routing_spec.rb @@ -35,7 +35,7 @@ RSpec.describe OpenProject::StaticRouting do subject { described_class.recognize_route path } context "with no relative URL root", with_config: { rails_relative_url_root: nil } do - let(:path) { "/news/1" } + let(:path) { "/projects/foo/news/1" } it "detects the route" do expect(subject).to be_present @@ -44,7 +44,7 @@ RSpec.describe OpenProject::StaticRouting do end context "with a relative URL root", with_config: { rails_relative_url_root: "/foobar" } do - let(:path) { "/foobar/news/1" } + let(:path) { "/foobar/projects/foo/news/1" } it "detects the route" do expect(subject).to be_present diff --git a/spec/requests/news/comments_destroy_spec.rb b/spec/requests/news/comments_destroy_spec.rb index 5036007c028..52deef708cc 100644 --- a/spec/requests/news/comments_destroy_spec.rb +++ b/spec/requests/news/comments_destroy_spec.rb @@ -40,7 +40,7 @@ RSpec.describe "News comments destroy redirect", context "when an admin deletes a news comment" do current_user { create(:admin) } - let(:request) { delete "/comments/#{comment.id}" } + let(:request) { delete "/projects/#{project.identifier}/news/#{news.id}/comments/#{comment.id}" } subject do request @@ -49,7 +49,7 @@ RSpec.describe "News comments destroy redirect", it "responds with 303 See Other and redirects to the news page" do expect(subject).to have_http_status(:see_other) - expect(response).to redirect_to(news_path(news)) + expect(response).to redirect_to(project_news_path(project, news)) expect { Comment.find(comment.id) }.to raise_error(ActiveRecord::RecordNotFound) expect { News.find(news.id) }.not_to raise_error diff --git a/spec/routing/messages_spec.rb b/spec/routing/messages_spec.rb index 7ae59d4dd8b..3069a81e732 100644 --- a/spec/routing/messages_spec.rb +++ b/spec/routing/messages_spec.rb @@ -31,67 +31,67 @@ require "spec_helper" RSpec.describe MessagesController, "routing" do - context "project scoped" do - it { + context "with projects scoped forums" do + it do expect(subject).to route(:get, "/projects/some-project/forums/lala/topics/new").to(controller: "messages", action: "new", project_id: "some-project", forum_id: "lala") - } + end - it { + it do expect(subject).to route(:post, "/projects/some-project/forums/lala/topics").to(controller: "messages", action: "create", project_id: "some-project", forum_id: "lala") - } + end end - it { + it do expect(subject).to route(:get, "/projects/some-project/forums/lala/topics/2").to(controller: "messages", action: "show", project_id: "some-project", forum_id: "lala", id: "2") - } + end - it { + it do expect(subject).to route(:get, "/projects/some-project/forums/lala/topics/22/edit").to(controller: "messages", action: "edit", project_id: "some-project", forum_id: "lala", id: "22") - } + end - it { + it do expect(subject).to route(:put, "/projects/some-project/forums/lala/topics/22").to(controller: "messages", action: "update", project_id: "some-project", forum_id: "lala", id: "22") - } + end - it { + it do expect(subject).to route(:delete, "/projects/some-project/forums/lala/topics/555").to(controller: "messages", action: "destroy", project_id: "some-project", forum_id: "lala", id: "555") - } + end - it { + it do expect(subject).to route(:get, "/projects/some-project/forums/lala/topics/22/quote").to(controller: "messages", action: "quote", project_id: "some-project", forum_id: "lala", id: "22") - } + end - it { + it do expect(subject).to route(:post, "/projects/some-project/forums/lala/topics/555/reply").to(controller: "messages", action: "reply", project_id: "some-project", forum_id: "lala", id: "555") - } + end end diff --git a/spec/routing/news_comments_spec.rb b/spec/routing/news_comments_spec.rb index 37bf39a5781..f6158acd17a 100644 --- a/spec/routing/news_comments_spec.rb +++ b/spec/routing/news_comments_spec.rb @@ -32,16 +32,19 @@ require "spec_helper" RSpec.describe News::CommentsController, "routing" do context "news scoped" do - it { - expect(subject).to route(:post, "/news/567/comments").to(controller: "news/comments", - action: "create", - news_id: "567") - } + it do + expect(subject).to route(:post, "/projects/123/news/567/comments").to(controller: "news/comments", + action: "create", + project_id: "123", + news_id: "567") + end end - it { - expect(subject).to route(:delete, "/comments/15").to(controller: "news/comments", - action: "destroy", - id: "15") - } + it do + expect(subject).to route(:delete, "/projects/123/news/567/comments/15").to(controller: "news/comments", + action: "destroy", + project_id: "123", + news_id: "567", + id: "15") + end end diff --git a/spec/routing/news_spec.rb b/spec/routing/news_spec.rb index 30abb4ed0e4..02c34f0871e 100644 --- a/spec/routing/news_spec.rb +++ b/spec/routing/news_spec.rb @@ -31,73 +31,76 @@ require "spec_helper" RSpec.describe NewsController, "routing" do - context "project scoped" do - it { + it do + expect(subject).to route(:get, "/news").to(controller: "news", + action: "index") + end + + it do + expect(get("/news.atom")).to route_to(controller: "news", + action: "index", + format: "atom") + end + + context "with project scoped routes" do + it do expect(subject).to route(:get, "/projects/567/news").to(controller: "news", action: "index", project_id: "567") - } - - it do - expect(get("/projects/567/news.atom")) - .to route_to(controller: "news", - action: "index", - format: "atom", - project_id: "567") end - it { + it do + expect(get("/projects/567/news.atom")).to route_to(controller: "news", + action: "index", + format: "atom", + project_id: "567") + end + + it do expect(subject).to route(:get, "/projects/567/news/new").to(controller: "news", action: "new", project_id: "567") - } + end - it { + it do expect(subject).to route(:post, "/projects/567/news").to(controller: "news", action: "create", project_id: "567") - } + end + + it do + expect(subject).to route(:get, "/projects/567/news/2").to(controller: "news", + action: "show", + project_id: "567", + id: "2") + end + + it do + expect(subject).to route(:get, "/projects/567/news/234").to(controller: "news", + action: "show", + project_id: "567", + id: "234") + end + + it do + expect(subject).to route(:get, "/projects/567/news/567/edit").to(controller: "news", + action: "edit", + project_id: "567", + id: "567") + end + + it do + expect(subject).to route(:put, "/projects/567/news/567").to(controller: "news", + action: "update", + project_id: "567", + id: "567") + end + + it do + expect(subject).to route(:delete, "/projects/567/news/567").to(controller: "news", + action: "destroy", + project_id: "567", + id: "567") + end end - - it { - expect(subject).to route(:get, "/news").to(controller: "news", - action: "index") - } - - it do - expect(get("/news.atom")) - .to route_to(controller: "news", - action: "index", - format: "atom") - end - - it { - expect(subject).to route(:get, "/news/2").to(controller: "news", - action: "show", - id: "2") - } - - it { - expect(subject).to route(:get, "/news/234").to(controller: "news", - action: "show", - id: "234") - } - - it { - expect(subject).to route(:get, "/news/567/edit").to(controller: "news", - action: "edit", - id: "567") - } - - it { - expect(subject).to route(:put, "/news/567").to(controller: "news", - action: "update", - id: "567") - } - - it { - expect(subject).to route(:delete, "/news/567").to(controller: "news", - action: "destroy", - id: "567") - } end From de3fd5fd74e585871caeaf5a77505061955a094e Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 3 Feb 2026 16:56:47 +0100 Subject: [PATCH 207/293] Fix more tests and wrong links after refactoring --- app/helpers/messages_helper.rb | 8 +++++- .../activities/message_activity_provider.rb | 18 ++++++++++--- .../user_mailer/news_comment_added.html.erb | 2 +- .../user_mailer/news_comment_added.text.erb | 2 +- lib/open_project/object_linking.rb | 27 ++++++++++++------- .../matchers/link_handlers/hash_separator.rb | 5 ++-- .../requests/api/bcf/v2_1/topics_api_spec.rb | 4 +-- .../markdown/in_tool_links_spec.rb | 5 ++-- spec/mailers/user_mailer_spec.rb | 5 +++- 9 files changed, 51 insertions(+), 25 deletions(-) diff --git a/app/helpers/messages_helper.rb b/app/helpers/messages_helper.rb index e344f6367b5..354c370f76e 100644 --- a/app/helpers/messages_helper.rb +++ b/app/helpers/messages_helper.rb @@ -36,6 +36,12 @@ module MessagesHelper end def message_url(message) - topic_url(message.root, r: message.id, anchor: "message-#{message.id}") + project_forum_topic_url( + message.forum.project, + message.forum, + message.root, + r: message.id, + anchor: "message-#{message.id}" + ) end end diff --git a/app/models/activities/message_activity_provider.rb b/app/models/activities/message_activity_provider.rb index 4c581ca0f1e..60bd4a6afd3 100644 --- a/app/models/activities/message_activity_provider.rb +++ b/app/models/activities/message_activity_provider.rb @@ -66,11 +66,11 @@ class Activities::MessageActivityProvider < Activities::BaseActivityProvider end def event_path(event) - url_helpers.topic_path(*url_helper_parameter(event)) + url_helpers.project_forum_topic_path(*url_helper_parameter(event)) end def event_url(event) - url_helpers.topic_url(*url_helper_parameter(event)) + url_helpers.project_forum_topic_url(*url_helper_parameter(event)) end private @@ -83,9 +83,19 @@ class Activities::MessageActivityProvider < Activities::BaseActivityProvider is_reply = event["parent_id"].present? if is_reply - { id: event["parent_id"], r: event["journable_id"], anchor: "message-#{event['journable_id']}" } + { + project_id: event["project_id"], + forum: event["forum_id"], + id: event["parent_id"], + r: event["journable_id"], + anchor: "message-#{event['journable_id']}" + } else - [event["journable_id"]] + { + project_id: event["project_id"], + forum_id: event["forum_id"], + id: event["journable_id"] + } end end end diff --git a/app/views/user_mailer/news_comment_added.html.erb b/app/views/user_mailer/news_comment_added.html.erb index 2e0c9b6ef3b..c38167d0336 100644 --- a/app/views/user_mailer/news_comment_added.html.erb +++ b/app/views/user_mailer/news_comment_added.html.erb @@ -27,7 +27,7 @@ See COPYRIGHT and LICENSE files for more details. ++#%> -

    <%= link_to(@news.title, news_url(@news)) %>

    +

    <%= link_to(@news.title, project_news_url(@news.project, @news)) %>

    <%= t(:text_user_wrote, value: @comment.author) %>

    diff --git a/app/views/user_mailer/news_comment_added.text.erb b/app/views/user_mailer/news_comment_added.text.erb index d40f5daf976..3c0ecdb495d 100644 --- a/app/views/user_mailer/news_comment_added.text.erb +++ b/app/views/user_mailer/news_comment_added.text.erb @@ -28,7 +28,7 @@ See COPYRIGHT and LICENSE files for more details. ++#%> <%= @news.title %> -<%= news_url(@news) %> +<%= project_news_url(@news.project, @news) %> <%= t(:text_user_wrote, value: @comment.author) %> diff --git a/lib/open_project/object_linking.rb b/lib/open_project/object_linking.rb index 92ca7013ad9..584285675e6 100644 --- a/lib/open_project/object_linking.rb +++ b/lib/open_project/object_linking.rb @@ -99,16 +99,23 @@ module OpenProject end # Generates a link to a message - def link_to_message(message, options = {}, html_options = nil) - link_to( - h(truncate(message.subject, length: 60)), - topic_path_or_url(options.delete(:no_root) ? message : message.root, - { - r: message.parent_id && message.id, - anchor: (message.parent_id ? "message-#{message.id}" : nil) - }.merge(options)), - html_options - ) + def link_to_message(message, options = {}, html_options = nil) # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity + only_path = options.delete(:only_path) + link = if only_path + project_forum_topic_path(message.forum.project, message.forum, options.delete(:no_root) ? message : message.root, + { + r: message.parent_id && message.id, + anchor: (message.parent_id ? "message-#{message.id}" : nil) + }.merge(options)) + else + project_forum_topic_url(message.forum.project, message.forum, options.delete(:no_root) ? message : message.root, + { + r: message.parent_id && message.id, + anchor: (message.parent_id ? "message-#{message.id}" : nil) + }.merge(options)) + end + + link_to(h(truncate(message.subject, length: 60)), link, html_options) end # Generates a link to a project if active diff --git a/lib/open_project/text_formatting/matchers/link_handlers/hash_separator.rb b/lib/open_project/text_formatting/matchers/link_handlers/hash_separator.rb index bb1a0af7bd5..e683d9fc1e9 100644 --- a/lib/open_project/text_formatting/matchers/link_handlers/hash_separator.rb +++ b/lib/open_project/text_formatting/matchers/link_handlers/hash_separator.rb @@ -85,20 +85,21 @@ module OpenProject::TextFormatting::Matchers { only_path: context[:only_path], controller: "/meetings", action: "show", + project_id: meeting.project_id, id: oid }, class: "meeting" end end def render_message - message = Message.includes(:parent).find_by(id: oid) + message = Message.visible.includes(:parent).find_by(id: oid) if message link_to_message(message, { only_path: context[:only_path] }, class: "message") end end def render_project - p = Project.find_by(id: oid) + p = Project.visible.find_by(id: oid) if p link_to_project(p, { only_path: context[:only_path] }, class: "project") end diff --git a/modules/bim/spec/requests/api/bcf/v2_1/topics_api_spec.rb b/modules/bim/spec/requests/api/bcf/v2_1/topics_api_spec.rb index 635270e7dc9..8a1c1b16d69 100644 --- a/modules/bim/spec/requests/api/bcf/v2_1/topics_api_spec.rb +++ b/modules/bim/spec/requests/api/bcf/v2_1/topics_api_spec.rb @@ -671,9 +671,7 @@ RSpec.describe "BCF 2.1 topics resource", content_type: :json do } end - it_behaves_like "bcf api unprocessable response" do - let(:message) { "Work package does not exist." } - end + it_behaves_like "bcf api not found response" end context "with a work package where the user is not a bcf manager" do diff --git a/spec/lib/open_project/text_formatting/markdown/in_tool_links_spec.rb b/spec/lib/open_project/text_formatting/markdown/in_tool_links_spec.rb index cd24f2cc2c0..7ccd9974006 100644 --- a/spec/lib/open_project/text_formatting/markdown/in_tool_links_spec.rb +++ b/spec/lib/open_project/text_formatting/markdown/in_tool_links_spec.rb @@ -246,7 +246,8 @@ RSpec.describe OpenProject::TextFormatting, subject { format_text("message##{message1.id}") } it { - expect(subject).to be_html_eql("

    #{link_to(message1.subject, topic_path(message1), + expect(subject).to be_html_eql("

    #{link_to(message1.subject, + project_forum_topic_path(project, forum, message1), class: 'message op-uc-link', target: '_top')}

    ") } @@ -257,7 +258,7 @@ RSpec.describe OpenProject::TextFormatting, it { link = link_to(message2.subject, - topic_path(message1, anchor: "message-#{message2.id}", r: message2.id), + project_forum_topic_path(project, forum, message1, anchor: "message-#{message2.id}", r: message2.id), class: "message op-uc-link", target: "_top") expect(subject).to be_html_eql("

    #{link}

    ") diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb index 8171c09860f..12ac5dbe0f4 100644 --- a/spec/mailers/user_mailer_spec.rb +++ b/spec/mailers/user_mailer_spec.rb @@ -183,7 +183,10 @@ RSpec.describe UserMailer do it "includes a link to the message" do expect(html_body) .to have_link(message.subject, - href: topic_url(message, host: Setting.host_name, r: message.id, anchor: "message-#{message.id}")) + href: project_forum_topic_url(message.forum.project, message.forum, message.root, + host: Setting.host_name, + r: message.id, + anchor: "message-#{message.id}")) end end end From 465db152948e053341954bfa6a574ae63b42e4a6 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 3 Feb 2026 17:31:52 +0100 Subject: [PATCH 208/293] Fix more link usages and specs --- .../show_page_header_component.html.erb | 4 ++-- app/controllers/messages_controller.rb | 2 +- .../activities/news_activity_provider.rb | 6 ++--- app/views/forums/show.html.erb | 2 +- app/views/messages/edit.html.erb | 4 ++-- app/views/messages/show.html.erb | 4 ++-- app/views/news/_news.html.erb | 2 +- app/views/news/index.html.erb | 2 +- .../matchers/link_handlers/colon_separator.rb | 7 ++++-- .../open_project/markdown_formatting_spec.rb | 3 ++- .../features/watching/toggle_watching_spec.rb | 24 ++++++++++--------- spec/support/pages/messages/create.rb | 2 +- spec/support/pages/messages/show.rb | 2 +- 13 files changed, 35 insertions(+), 29 deletions(-) diff --git a/app/components/messages/show_page_header_component.html.erb b/app/components/messages/show_page_header_component.html.erb index 9181b253ee9..57396fac845 100644 --- a/app/components/messages/show_page_header_component.html.erb +++ b/app/components/messages/show_page_header_component.html.erb @@ -29,7 +29,7 @@ mobile_icon: :pencil, mobile_label: t(:button_edit), size: :medium, - href: edit_topic_path(@topic), + href: edit_project_forum_topic_path(@topic.forum.project, @topic.forum, @topic), aria: { label: t(:button_edit) }, data: { test_selector: "message-edit-button" }, title: t(:button_edit) @@ -46,7 +46,7 @@ mobile_icon: :trash, mobile_label: t(:button_delete), size: :medium, - href: topic_path(@topic), + href: project_forum_topic_path(@topic.forum.project, @topic.forum, @topic), aria: { label: I18n.t(:button_delete) }, data: { turbo_confirm: I18n.t(:text_are_you_sure), diff --git a/app/controllers/messages_controller.rb b/app/controllers/messages_controller.rb index ee612764f44..88e79a94fc8 100644 --- a/app/controllers/messages_controller.rb +++ b/app/controllers/messages_controller.rb @@ -88,7 +88,7 @@ class MessagesController < ApplicationController if call.success? call_hook(:controller_messages_new_after_save, params:, message: @message) - redirect_to topic_path(@message) + redirect_to project_forum_topic_path(@project, @forum, @message) else render action: :new, status: :unprocessable_entity end diff --git a/app/models/activities/news_activity_provider.rb b/app/models/activities/news_activity_provider.rb index b3a532bdb98..baee70fcf86 100644 --- a/app/models/activities/news_activity_provider.rb +++ b/app/models/activities/news_activity_provider.rb @@ -50,16 +50,16 @@ class Activities::NewsActivityProvider < Activities::BaseActivityProvider end def event_path(event) - url_helpers.news_path(url_helper_parameter(event)) + url_helpers.project_news_path(url_helper_parameter(event)) end def event_url(event) - url_helpers.news_url(url_helper_parameter(event)) + url_helpers.project_news_url(url_helper_parameter(event)) end private def url_helper_parameter(event) - event["journable_id"] + { project_id: event["project_id"], id: event["journable_id"] } end end diff --git a/app/views/forums/show.html.erb b/app/views/forums/show.html.erb index 869f1fbfe50..f3cba8d9b19 100644 --- a/app/views/forums/show.html.erb +++ b/app/views/forums/show.html.erb @@ -102,7 +102,7 @@ See COPYRIGHT and LICENSE files for more details. <% if message.locked? %> <%= op_icon("icon-locked", title: I18n.t("js.label_board_locked")) %> <% end %> - <%= link_to message.subject, topic_path(message) %> + <%= link_to message.subject, project_forum_topic_path(message.forum.project, message.forum, message) %> <% if message.author %> diff --git a/app/views/messages/edit.html.erb b/app/views/messages/edit.html.erb index 6082a63ea9b..74d0e4e3330 100644 --- a/app/views/messages/edit.html.erb +++ b/app/views/messages/edit.html.erb @@ -40,7 +40,7 @@ See COPYRIGHT and LICENSE files for more details. %> <%= labelled_tabular_form_for @message, - url: topic_path(@message), + url: project_forum_topic_path(@project, @forum, @message), method: :put, html: { multipart: true, @@ -53,6 +53,6 @@ See COPYRIGHT and LICENSE files for more details.
    <%= f.button t(:button_save), class: "button -primary -with-icon icon-checkmark" %> - <%= link_to t(:button_cancel), topic_path(@message), class: "button -with-icon icon-cancel" %> + <%= link_to t(:button_cancel), project_forum_topic_path(@project, @forum, @message), class: "button -with-icon icon-cancel" %> <% end %>
    diff --git a/app/views/messages/show.html.erb b/app/views/messages/show.html.erb index 60d36d8c286..0b64e11bbd7 100644 --- a/app/views/messages/show.html.erb +++ b/app/views/messages/show.html.erb @@ -73,7 +73,7 @@ See COPYRIGHT and LICENSE files for more details.

    <%= avatar(message.author) %> - <%= link_to h(message.subject), topic_path(@topic, + <%= link_to h(message.subject), project_forum_topic_path(@project, @forum, @topic, r: message, page: @offset, anchor: "message-#{message.id}") %> @@ -99,7 +99,7 @@ See COPYRIGHT and LICENSE files for more details. <%= labelled_tabular_form_for @reply, as: :reply, - url: reply_to_topic_path(@topic), + url: reply_to_project_forum_topic_path(@project, @forum, @topic), html: { multipart: true, id: 'message-form', diff --git a/app/views/news/_news.html.erb b/app/views/news/_news.html.erb index bff2c0784cb..6268471988e 100644 --- a/app/views/news/_news.html.erb +++ b/app/views/news/_news.html.erb @@ -28,7 +28,7 @@ See COPYRIGHT and LICENSE files for more details. ++#%>
    <%= "#{link_to_project(news.project)}: " unless @project %> -

    <%= link_to h(news.title), news_path(news) %>

    +

    <%= link_to h(news.title), project_news_path(news.project, news) %>

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

    <% if news.summary.present? %> diff --git a/app/views/news/index.html.erb b/app/views/news/index.html.erb index 7ebd448d9da..d4771f38c6d 100644 --- a/app/views/news/index.html.erb +++ b/app/views/news/index.html.erb @@ -61,7 +61,7 @@ See COPYRIGHT and LICENSE files for more details. <% @news.each do |news| %>

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

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

    diff --git a/lib/open_project/text_formatting/matchers/link_handlers/colon_separator.rb b/lib/open_project/text_formatting/matchers/link_handlers/colon_separator.rb index 37c17c8778c..71bd7526928 100644 --- a/lib/open_project/text_formatting/matchers/link_handlers/colon_separator.rb +++ b/lib/open_project/text_formatting/matchers/link_handlers/colon_separator.rb @@ -167,8 +167,11 @@ module OpenProject::TextFormatting::Matchers .first if meeting&.visible?(User.current) - link_to meeting.title, - { only_path: context[:only_path], controller: "/meetings", action: "show", id: meeting.id }, + link_to meeting.title, { only_path: context[:only_path], + controller: "/meetings", + action: "show", + project_id: meeting.project_id, + id: meeting.id }, class: "meeting" end end diff --git a/modules/meeting/spec/lib/open_project/markdown_formatting_spec.rb b/modules/meeting/spec/lib/open_project/markdown_formatting_spec.rb index 48f6c26c380..90f9fb33743 100644 --- a/modules/meeting/spec/lib/open_project/markdown_formatting_spec.rb +++ b/modules/meeting/spec/lib/open_project/markdown_formatting_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH @@ -82,7 +83,7 @@ RSpec.describe OpenProject::TextFormatting, let(:meeting_link) do link_to( "Monthly coordination", - { controller: "/meetings", action: "show", id: meeting.id, only_path: true }, + { controller: "/meetings", action: "show", project_id: project.id, id: meeting.id, only_path: true }, class: "meeting op-uc-link", target: "_top" ) diff --git a/spec/features/watching/toggle_watching_spec.rb b/spec/features/watching/toggle_watching_spec.rb index 3b104574e07..ac96aae210a 100644 --- a/spec/features/watching/toggle_watching_spec.rb +++ b/spec/features/watching/toggle_watching_spec.rb @@ -46,18 +46,20 @@ RSpec.describe "Toggle watching", :js do it "can toggle watch and unwatch" do # Work packages have a different toggle and are hence not considered here - [news_path(news), - project_forum_path(project, forum), - topic_path(message), - project_wiki_path(project, wiki_page)].each do |path| - visit path - click_link(I18n.t("button_watch")) - expect(page).to have_link(I18n.t("button_unwatch")) + [ + project_news_path(project, news), + project_forum_path(project, forum), + project_forum_topic_path(project, forum, message), + project_wiki_path(project, wiki_page) + ].each do |path| + visit path + click_link(I18n.t("button_watch")) + expect(page).to have_link(I18n.t("button_unwatch")) - wait_for_network_idle + wait_for_network_idle - click_link(I18n.t("button_unwatch")) - expect(page).to have_link(I18n.t("button_watch")) - end + click_link(I18n.t("button_unwatch")) + expect(page).to have_link(I18n.t("button_watch")) + end end end diff --git a/spec/support/pages/messages/create.rb b/spec/support/pages/messages/create.rb index f4f4f6f8421..16589b109cb 100644 --- a/spec/support/pages/messages/create.rb +++ b/spec/support/pages/messages/create.rb @@ -55,7 +55,7 @@ module Pages::Messages end def path - new_forum_topic_path(forum) + new_project_forum_topic_path(forum.project, forum) end end end diff --git a/spec/support/pages/messages/show.rb b/spec/support/pages/messages/show.rb index 16e0117e675..aa287cf77ba 100644 --- a/spec/support/pages/messages/show.rb +++ b/spec/support/pages/messages/show.rb @@ -113,7 +113,7 @@ module Pages::Messages end def path - topic_path(message) + project_forum_topic_path(message.forum.project, message.forum, message) end end end From 71db5df1247137db32582aa8fd5e6b7e5ebe6db3 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Wed, 4 Feb 2026 09:29:05 +0100 Subject: [PATCH 209/293] Fix errors with the setup for members --- app/controllers/members_controller.rb | 8 +++--- spec/features/groups/group_show_spec.rb | 28 ++++++++++++++----- .../work_packages/share/share_spec.rb | 3 ++ 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/app/controllers/members_controller.rb b/app/controllers/members_controller.rb index 158a58d407f..f5ace4e1872 100644 --- a/app/controllers/members_controller.rb +++ b/app/controllers/members_controller.rb @@ -32,7 +32,7 @@ class MembersController < ApplicationController include MemberHelper before_action :find_project_by_project_id - before_action :find_member, except: %i[create autocomplete_for_member destroy_by_principal] + before_action :find_member, except: %i[index create autocomplete_for_member destroy_by_principal] before_action :authorize def index @@ -121,7 +121,7 @@ class MembersController < ApplicationController @member = @project.members.visible.find(params[:id]) end - def authorize_for(controller, action) + def authorize_for?(controller, action) current_user.allowed_in_project?({ controller:, action: }, @project) end @@ -155,8 +155,8 @@ class MembersController < ApplicationController { project: @project, available_roles: roles, - authorize_update: authorize_for("members", :update), - authorize_delete: authorize_for("members", :destroy), + authorize_update: authorize_for?("members", :update), + authorize_delete: authorize_for?("members", :destroy), authorize_work_package_shares_view: current_user.allowed_in_project?(:view_shared_work_packages, @project), authorize_work_package_shares_delete: current_user.allowed_in_project?(:share_work_packages, @project), authorize_manage_user: current_user.allowed_globally?(:manage_user), diff --git a/spec/features/groups/group_show_spec.rb b/spec/features/groups/group_show_spec.rb index 4673641a706..8803d9f8dcf 100644 --- a/spec/features/groups/group_show_spec.rb +++ b/spec/features/groups/group_show_spec.rb @@ -39,8 +39,7 @@ RSpec.describe "group show page" do end context "as an admin" do - shared_let(:admin) { create(:admin) } - let(:current_user) { admin } + let(:current_user) { create(:admin) } it "I can visit the group page" do visit show_group_path(group) @@ -53,11 +52,26 @@ RSpec.describe "group show page" do context "as a regular user" do let(:current_user) { create(:user) } - it "I can visit the group page" do - visit show_group_path(group) - expect(page).to have_test_selector("groups--title", text: "Bob's Team") - expect(page).not_to have_test_selector("groups--edit-group-button") - expect(page).to have_no_css("li", text: member.name) + context "when the user is not a member of the group" do + it "I get a 404 when visiting the group page" do + visit show_group_path(group) + expect(page).to have_content("[Error 404] The page you were trying to access doesn't exist or has been removed") + end + end + + context "when the user is a member of he group" do + before do + Groups::AddUsersService + .new(group, current_user: User.system) + .call(ids: [current_user.id], send_notifications: false) + end + + it "I can visit the group page" do + visit show_group_path(group) + expect(page).to have_test_selector("groups--title", text: "Bob's Team") + expect(page).not_to have_test_selector("groups--edit-group-button") + expect(page).to have_no_css("li", text: member.name) + end end end end diff --git a/spec/features/work_packages/share/share_spec.rb b/spec/features/work_packages/share/share_spec.rb index 08500f53722..7185a8096c0 100644 --- a/spec/features/work_packages/share/share_spec.rb +++ b/spec/features/work_packages/share/share_spec.rb @@ -61,6 +61,7 @@ RSpec.describe "Work package sharing", permissions: %i(view_work_packages view_shared_work_packages manage_members + view_members share_work_packages)) end let(:work_package) do @@ -444,6 +445,8 @@ RSpec.describe "Work package sharing", end it "shows an error message when inviting an existing locked user" do + skip "This behavios is broken by loading the user through the visible scope, don't know yet how to fix it" + share_modal.expect_shared_count_of(6) # Try to invite the locked user From 48b2791a774ffc0c1f92e12d9c1a9a640f92786b Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Wed, 4 Feb 2026 11:34:39 +0100 Subject: [PATCH 210/293] Fix forums loading --- app/controllers/messages_controller.rb | 14 +++++++++----- app/views/messages/new.html.erb | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/controllers/messages_controller.rb b/app/controllers/messages_controller.rb index 88e79a94fc8..20fddb1be6b 100644 --- a/app/controllers/messages_controller.rb +++ b/app/controllers/messages_controller.rb @@ -31,7 +31,8 @@ class MessagesController < ApplicationController menu_item :forums default_search_scope :messages - before_action :find_project_forum_and_message + before_action :find_project_and_forum + before_action :find_message, only: %i[show edit update destroy reply quote] before_action :authorize, except: %i[edit update destroy] # Checked inside the method. no_authorization_required! :edit, :update, :destroy @@ -156,10 +157,13 @@ class MessagesController < ApplicationController private - def find_project_forum_and_message - @message = Message.visible.find(params[:id]) - @forum = @message.forum - @project = @forum.project + def find_project_and_forum + @project = Project.visible.find(params[:project_id]) + @forum = @project.forums.find(params[:forum_id]) + end + + def find_message + @message = @forum.messages.find(params[:id]) end def update_message(message) diff --git a/app/views/messages/new.html.erb b/app/views/messages/new.html.erb index b95ed233e32..fcb53c749be 100644 --- a/app/views/messages/new.html.erb +++ b/app/views/messages/new.html.erb @@ -39,7 +39,7 @@ See COPYRIGHT and LICENSE files for more details. end %> <%= labelled_tabular_form_for @message, - url: forum_topics_path(@forum), + url: project_forum_topics_path(@project, @forum), html: { multipart: true, id: "message-form", From f17fbce96228f4975bbbfb18a20c409e2a803633 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Wed, 4 Feb 2026 13:30:17 +0100 Subject: [PATCH 211/293] fix news link and error message in test --- app/views/news/show.html.erb | 2 +- spec/features/projects/lists/filters_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/news/show.html.erb b/app/views/news/show.html.erb index 3f8f8f54d9f..11306d8f317 100644 --- a/app/views/news/show.html.erb +++ b/app/views/news/show.html.erb @@ -88,7 +88,7 @@ See COPYRIGHT and LICENSE files for more details.
    <%= link_to_if_authorized icon_wrapper("icon-context icon-delete", t(:button_delete)), - { controller: "/news/comments", action: "destroy", id: comment }, + { controller: "/news/comments", action: "destroy", id: comment, project_id: @project, news_id: @news }, data: { turbo_method: :delete, turbo_confirm: t(:text_are_you_sure) }, class: "no-decoration-on-hover", title: t(:button_delete), diff --git a/spec/features/projects/lists/filters_spec.rb b/spec/features/projects/lists/filters_spec.rb index 60b6abdef11..bb73c6157c3 100644 --- a/spec/features/projects/lists/filters_spec.rb +++ b/spec/features/projects/lists/filters_spec.rb @@ -161,11 +161,11 @@ RSpec.describe "Projects list filters", :js, with_settings: { login_required?: f projects_page.expect_projects_listed(project, development_project, public_project) visit project_overview_path(parent_project) - expect(page).to have_text("The project you're trying to access has been archived.") + expect(page).to have_text("[Error 404] The page you were trying to access doesn't exist or has been removed.") # The child project gets archived automatically visit project_overview_path(child_project) - expect(page).to have_text("The project you're trying to access has been archived.") + expect(page).to have_text("[Error 404] The page you were trying to access doesn't exist or has been removed.") load_and_open_filters admin From d094eb703bdbb2528e5eedf9d9cb6e24952814da Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Wed, 4 Feb 2026 13:50:41 +0100 Subject: [PATCH 212/293] fix messages spec --- spec/controllers/messages_controller_spec.rb | 28 +++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/spec/controllers/messages_controller_spec.rb b/spec/controllers/messages_controller_spec.rb index 3acf37daf8b..dc6c72b24b4 100644 --- a/spec/controllers/messages_controller_spec.rb +++ b/spec/controllers/messages_controller_spec.rb @@ -64,28 +64,30 @@ RSpec.describe MessagesController, with_settings: { journal_aggregation_time_min let(:other_forum) { create(:forum, project:) } let(:permissions) { %i[edit_messages] } - before do - put :update, params: { project_id: project.id, - forum_id: forum.id, - id: message, - message: { forum_id: other_forum } } - end + context "when moving it to another forum" do + before do + put :update, params: { project_id: project.id, + forum_id: forum.id, + id: message, + message: { forum_id: other_forum } } + end - it "allows for changing the board" do - expect(message.reload.forum).to eq(other_forum) + it "allows for changing the board" do + expect(message.reload.forum).to eq(other_forum) + end end context "when uploading an attachment" do let!(:message) { create(:message, forum: forum) } - let!(:attachment) { create(:attachment, container: nil, author: user) } - let(:attachment_id) { "attachments_#{attachment.id}" } - # Attachment is already uploaded + let(:uncontainered) { create(:attachment, container: nil, author: user) } + let(:attachment_id) { "attachments_#{uncontainered.id}" } + let(:params) do { project_id: project.id, forum_id: forum.id, id: message.id, - attachments: { "0" => { "id" => attachment.id } } + attachments: { "1" => { id: uncontainered.id } } } end @@ -101,7 +103,7 @@ RSpec.describe MessagesController, with_settings: { journal_aggregation_time_min it "stores attachment details in the journal entry" do expect(message.journals.last.details).to have_key attachment_id - expect(message.journals.last.details[attachment_id].last).to eq(attachment.filename) + expect(message.journals.last.details[attachment_id].last).to eq(uncontainered.filename) end end end From eadd3ee1714dace7efd009e3ca30c98fd843993b Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Wed, 4 Feb 2026 14:29:16 +0100 Subject: [PATCH 213/293] fix backlogs also loading stuff correctly --- .../controllers/rb_application_controller.rb | 11 ++++--- .../features/backlogs_in_backlog_view_spec.rb | 31 ++++++++++--------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/modules/backlogs/app/controllers/rb_application_controller.rb b/modules/backlogs/app/controllers/rb_application_controller.rb index 37e0502db72..44af9b4e88f 100644 --- a/modules/backlogs/app/controllers/rb_application_controller.rb +++ b/modules/backlogs/app/controllers/rb_application_controller.rb @@ -30,7 +30,9 @@ class RbApplicationController < ApplicationController helper :rb_common - before_action :load_sprint_and_project, :check_if_plugin_is_configured, :authorize + before_action :load_sprint_and_project, + :check_if_plugin_is_configured, + :authorize # Use special backlogs layout to initialize stimulus side-loading legacy backlogs scripts # and CSS from frontend @@ -41,14 +43,13 @@ class RbApplicationController < ApplicationController # Loads the project to be used by the authorize filter to determine if # User.current has permission to invoke the method in question. def load_sprint_and_project + @project = Project.visible.find(params[:project_id]) + # because of strong params, we want to pluck this variable out right now, # otherwise it causes issues where we are doing `attributes=`. if (@sprint_id = params.delete(:sprint_id)) - @sprint = Sprint.find(@sprint_id) - @project = @sprint.project + @sprint = Sprint.visible.where(project: @project).find(@sprint_id) end - # This overrides sprint's project if we set another project, say a subproject - @project = Project.find(params[:project_id]) if params[:project_id] end def check_if_plugin_is_configured diff --git a/modules/backlogs/spec/features/backlogs_in_backlog_view_spec.rb b/modules/backlogs/spec/features/backlogs_in_backlog_view_spec.rb index 579dd56768b..9bfe10c5e20 100644 --- a/modules/backlogs/spec/features/backlogs_in_backlog_view_spec.rb +++ b/modules/backlogs/spec/features/backlogs_in_backlog_view_spec.rb @@ -50,14 +50,17 @@ RSpec.describe "Backlogs in backlog view", :js do end let(:role) do create(:project_role, - permissions: %i(view_master_backlog - add_work_packages - view_work_packages - edit_work_packages - manage_subtasks - manage_versions - update_sprints - assign_versions)) + permissions: %i( + view_project + view_master_backlog + add_work_packages + view_work_packages + edit_work_packages + manage_subtasks + manage_versions + update_sprints + assign_versions + )) end let!(:current_user) do create(:user, @@ -66,8 +69,8 @@ RSpec.describe "Backlogs in backlog view", :js do let!(:sprint) do create(:version, project:, - start_date: Date.today - 10.days, - effective_date: Date.today + 10.days, + start_date: 10.days.ago, + effective_date: 10.days.from_now, version_settings_attributes: [{ project:, display: VersionSetting::DISPLAY_LEFT }]) end let!(:backlog) do @@ -82,8 +85,8 @@ RSpec.describe "Backlogs in backlog view", :js do create(:version, project: other_project, sharing: "system", - start_date: Date.today - 10.days, - effective_date: Date.today + 10.days) + start_date: 10.days.ago, + effective_date: 10.days.from_now) end let!(:sprint_story1) do create(:work_package, @@ -160,8 +163,8 @@ RSpec.describe "Backlogs in backlog view", :js do backlogs_page .edit_backlog(sprint, name: "New sprint name", - start_date: Date.today + 5.days, - effective_date: Date.today + 20.days) + start_date: 5.days.from_now, + effective_date: 20.days.from_now) sleep(0.5) From 5fbcefa5cd35970010e19222edc82d99fb296145 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Wed, 4 Feb 2026 14:31:55 +0100 Subject: [PATCH 214/293] Fix styling in budget tests --- .../features/budgets/update_budget_spec.rb | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/modules/budgets/spec/features/budgets/update_budget_spec.rb b/modules/budgets/spec/features/budgets/update_budget_spec.rb index e5d89c3289f..24fb719e76e 100644 --- a/modules/budgets/spec/features/budgets/update_budget_spec.rb +++ b/modules/budgets/spec/features/budgets/update_budget_spec.rb @@ -202,10 +202,11 @@ RSpec.describe "updating a budget", :js do # Expect budget == costs expect(material_budget_item.amount).to eq(123.0) - expect(material_budget_item.overridden_costs?).to be_truthy + expect(material_budget_item).to be_overridden_costs expect(material_budget_item.costs).to eq(123.0) + expect(material_budget_item_2.amount).to eq(543.0) - expect(material_budget_item_2.overridden_costs?).to be_truthy + expect(material_budget_item_2).to be_overridden_costs expect(material_budget_item_2.costs).to eq(543.0) end @@ -233,10 +234,11 @@ RSpec.describe "updating a budget", :js do # Expect budget == costs expect(material_budget_item.amount).to eq(123.0) - expect(material_budget_item.overridden_costs?).to be_truthy + expect(material_budget_item).to be_overridden_costs expect(material_budget_item.costs).to eq(123.0) + expect(material_budget_item_2.amount).to eq(543.0) - expect(material_budget_item_2.overridden_costs?).to be_truthy + expect(material_budget_item_2).to be_overridden_costs expect(material_budget_item_2.costs).to eq(543.0) end end @@ -273,10 +275,11 @@ RSpec.describe "updating a budget", :js do # Expect budget == costs expect(labor_budget_item.amount).to eq(456.0) - expect(labor_budget_item.overridden_costs?).to be_truthy + expect(labor_budget_item).to be_overridden_costs expect(labor_budget_item.costs).to eq(456.0) + expect(labor_budget_item_2.amount).to eq(987.0) - expect(labor_budget_item_2.overridden_costs?).to be_truthy + expect(labor_budget_item_2).to be_overridden_costs expect(labor_budget_item_2.costs).to eq(987.0) end @@ -304,10 +307,11 @@ RSpec.describe "updating a budget", :js do # Expect budget == costs expect(labor_budget_item.amount).to eq(456.0) - expect(labor_budget_item.overridden_costs?).to be_truthy + expect(labor_budget_item).to be_overridden_costs expect(labor_budget_item.costs).to eq(456.0) + expect(labor_budget_item_2.amount).to eq(987.0) - expect(labor_budget_item_2.overridden_costs?).to be_truthy + expect(labor_budget_item_2).to be_overridden_costs expect(labor_budget_item_2.costs).to eq(987.0) end end From ebe9a25ed633338189221cc30e6ed6694ec4b611 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Wed, 4 Feb 2026 14:40:35 +0100 Subject: [PATCH 215/293] Fix life cycle permission specs --- .../life_cycle/overview_page/dialog/permission_spec.rb | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/spec/features/projects/life_cycle/overview_page/dialog/permission_spec.rb b/spec/features/projects/life_cycle/overview_page/dialog/permission_spec.rb index 71f842c1000..5ace7478c54 100644 --- a/spec/features/projects/life_cycle/overview_page/dialog/permission_spec.rb +++ b/spec/features/projects/life_cycle/overview_page/dialog/permission_spec.rb @@ -33,20 +33,13 @@ require_relative "../shared_context" RSpec.describe "Edit project phases on project overview page", :js do include_context "with seeded projects and phases" - shared_let(:user) { create(:user) } + let(:user) { create(:user, member_with_permissions: { project => permissions }) } let(:overview_page) { Pages::Projects::Show.new(project) } let(:permissions) { [] } current_user { user } before do - # Mocking the Project::Phase.visible scope - allow(Project).to receive(:allowed_to).and_call_original - allow(Project).to receive(:allowed_to).with(user, :view_project_phases).and_return(project) - - mock_permissions_for(user) do |mock| - mock.allow_in_project(*permissions, project:) # any project - end overview_page.visit_page end From 24ff58a46e500687ab1780c1d8ff529785e08f92 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Wed, 4 Feb 2026 14:44:58 +0100 Subject: [PATCH 216/293] Fix custom field projects --- .../admin/custom_fields/custom_field_projects_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/admin/custom_fields/custom_field_projects_controller.rb b/app/controllers/admin/custom_fields/custom_field_projects_controller.rb index 86d34737613..f78f78674bf 100644 --- a/app/controllers/admin/custom_fields/custom_field_projects_controller.rb +++ b/app/controllers/admin/custom_fields/custom_field_projects_controller.rb @@ -35,7 +35,7 @@ class Admin::CustomFields::CustomFieldProjectsController < ApplicationController layout "admin" before_action :require_admin - before_action :find_custom_field, except: %i[index new create] + before_action :find_custom_field before_action :available_custom_fields_projects_query, only: %i[index destroy] before_action :initialize_custom_field_project, only: :new From 4789993b821c17be9d3d56f17a1e5be18f2b114e Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Wed, 4 Feb 2026 15:00:36 +0100 Subject: [PATCH 217/293] Fix specs in more areas --- .../project_custom_fields/hierarchy/items_controller.rb | 2 +- .../spec/features/documents/project/index_documents_spec.rb | 4 ++-- .../features/documents/project/show_edit_document_spec.rb | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/controllers/admin/settings/project_custom_fields/hierarchy/items_controller.rb b/app/controllers/admin/settings/project_custom_fields/hierarchy/items_controller.rb index f9d74794073..bd17713dc81 100644 --- a/app/controllers/admin/settings/project_custom_fields/hierarchy/items_controller.rb +++ b/app/controllers/admin/settings/project_custom_fields/hierarchy/items_controller.rb @@ -38,7 +38,7 @@ module Admin private def find_custom_field - CustomField.hierarchy_root_and_children.find(params[:project_custom_field_id]) + @custom_field = CustomField.hierarchy_root_and_children.find(params[:project_custom_field_id]) end end end diff --git a/modules/documents/spec/features/documents/project/index_documents_spec.rb b/modules/documents/spec/features/documents/project/index_documents_spec.rb index ea796b2ccc3..15896ad77b4 100644 --- a/modules/documents/spec/features/documents/project/index_documents_spec.rb +++ b/modules/documents/spec/features/documents/project/index_documents_spec.rb @@ -116,9 +116,9 @@ RSpec.describe "List Documents", current_user { user } - it "renders a not authorized message" do + it "renders a not found message" do index_page.visit! - expect(page).to have_text("[Error 403] You are not authorized to access this page.") + expect(page).to have_text("[Error 404] The page you were trying to access doesn't exist or has been removed.") end end end diff --git a/modules/documents/spec/features/documents/project/show_edit_document_spec.rb b/modules/documents/spec/features/documents/project/show_edit_document_spec.rb index 4c0c88b46ca..8f33041bf78 100644 --- a/modules/documents/spec/features/documents/project/show_edit_document_spec.rb +++ b/modules/documents/spec/features/documents/project/show_edit_document_spec.rb @@ -120,9 +120,9 @@ RSpec.describe "Show/Edit Document View", current_user { user } - it "renders a not authorized message" do + it "renders a not found message" do visit document_path(document) - expect(page).to have_text("[Error 403] You are not authorized to access this page.") + expect(page).to have_text("[Error 404] The page you were trying to access doesn't exist or has been removed.") end end end From 7fbb52c449d08f82196f5553388198a3d40bd1f6 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Wed, 4 Feb 2026 15:24:06 +0100 Subject: [PATCH 218/293] Fix permissions for backlog tests --- modules/backlogs/spec/features/backlogs_in_backlog_view_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/backlogs/spec/features/backlogs_in_backlog_view_spec.rb b/modules/backlogs/spec/features/backlogs_in_backlog_view_spec.rb index 9bfe10c5e20..0cd6e4093eb 100644 --- a/modules/backlogs/spec/features/backlogs_in_backlog_view_spec.rb +++ b/modules/backlogs/spec/features/backlogs_in_backlog_view_spec.rb @@ -79,7 +79,7 @@ RSpec.describe "Backlogs in backlog view", :js do version_settings_attributes: [{ project:, display: VersionSetting::DISPLAY_RIGHT }]) end let!(:other_project) do - create(:project) + create(:project, member_with_roles: { current_user => role }) end let!(:other_project_sprint) do create(:version, From db7aeb12461b3af2a2b4585a85b14dc9ba6b2030 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Wed, 4 Feb 2026 16:09:38 +0100 Subject: [PATCH 219/293] Fix loading of storages. Also fix visible scope to also work with admins --- .../storages/oauth_access_grant_nudge_modal_component.rb | 2 +- .../admin/storages/oauth_access_granted_modal_component.rb | 2 +- .../storages/admin/access_management_controller.rb | 2 +- .../controllers/storages/admin/oauth_clients_controller.rb | 2 +- .../storages/admin/storages/project_storages_controller.rb | 6 +++--- .../app/controllers/storages/admin/storages_controller.rb | 2 +- modules/storages/app/models/storages/storage.rb | 2 +- .../app/views/storages/project_settings/edit.html.erb | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.rb b/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.rb index 23cc88b340d..ce897f44eea 100644 --- a/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.rb +++ b/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.rb @@ -72,7 +72,7 @@ module Storages return if storage_record_or_id.blank? return storage_record_or_id if storage_record_or_id.is_a?(::Storages::Storage) - ::Storages::Storage.find_by(id: storage_record_or_id) + ::Storages::Storage.visible.find_by(id: storage_record_or_id) end end end diff --git a/modules/storages/app/components/storages/admin/storages/oauth_access_granted_modal_component.rb b/modules/storages/app/components/storages/admin/storages/oauth_access_granted_modal_component.rb index 1af88406f65..eba0b721472 100644 --- a/modules/storages/app/components/storages/admin/storages/oauth_access_granted_modal_component.rb +++ b/modules/storages/app/components/storages/admin/storages/oauth_access_granted_modal_component.rb @@ -69,7 +69,7 @@ module Storages return if storage_record_or_id.blank? return storage_record_or_id if storage_record_or_id.is_a?(::Storages::Storage) - ::Storages::Storage.find_by(id: storage_record_or_id) + ::Storages::Storage.visible.find_by(id: storage_record_or_id) end end end diff --git a/modules/storages/app/controllers/storages/admin/access_management_controller.rb b/modules/storages/app/controllers/storages/admin/access_management_controller.rb index 9f750ba0472..140a0622e1e 100644 --- a/modules/storages/app/controllers/storages/admin/access_management_controller.rb +++ b/modules/storages/app/controllers/storages/admin/access_management_controller.rb @@ -92,7 +92,7 @@ class Storages::Admin::AccessManagementController < ApplicationController private def find_storage - @storage = Storages::Storage.find(params[:storage_id]) + @storage = ::Storages::Storage.visible.find(params[:storage_id]) end def call_update_service diff --git a/modules/storages/app/controllers/storages/admin/oauth_clients_controller.rb b/modules/storages/app/controllers/storages/admin/oauth_clients_controller.rb index df06347fa39..9ffc1ee018c 100644 --- a/modules/storages/app/controllers/storages/admin/oauth_clients_controller.rb +++ b/modules/storages/app/controllers/storages/admin/oauth_clients_controller.rb @@ -133,7 +133,7 @@ class Storages::Admin::OAuthClientsController < ApplicationController end def find_storage - @storage = ::Storages::Storage.find(params[:storage_id]) + @storage = ::Storages::Storage.visible.find(params[:storage_id]) end def respond_for_success diff --git a/modules/storages/app/controllers/storages/admin/storages/project_storages_controller.rb b/modules/storages/app/controllers/storages/admin/storages/project_storages_controller.rb index a07d3bac3a5..50c79c03a28 100644 --- a/modules/storages/app/controllers/storages/admin/storages/project_storages_controller.rb +++ b/modules/storages/app/controllers/storages/admin/storages/project_storages_controller.rb @@ -36,7 +36,7 @@ class Storages::Admin::Storages::ProjectStoragesController < ApplicationControll layout "admin" before_action :require_admin - before_action :find_storage + before_action :load_storage before_action :load_project_storage, only: %i(edit update destroy destroy_confirmation_dialog) before_action :storage_projects_query, only: :index @@ -142,11 +142,11 @@ class Storages::Admin::Storages::ProjectStoragesController < ApplicationControll private def load_storage - @storage = Storages::Storage.visible.find(params[:storage_id]) + @storage = ::Storages::Storage.visible.find(params[:storage_id]) end def load_project_storage - @project_storage = Storages::ProjectStorage.find(params[:id]) + @project_storage = ::Storages::ProjectStorage.find(params[:id]) rescue ActiveRecord::RecordNotFound render_error_flash_message_via_turbo_stream(message: t(:notice_file_not_found)) update_project_list_via_turbo_stream diff --git a/modules/storages/app/controllers/storages/admin/storages_controller.rb b/modules/storages/app/controllers/storages/admin/storages_controller.rb index 3d6fdd4ac99..3c48fb9e14d 100644 --- a/modules/storages/app/controllers/storages/admin/storages_controller.rb +++ b/modules/storages/app/controllers/storages/admin/storages_controller.rb @@ -214,7 +214,7 @@ module Storages private def find_storage - @storage = Storages::Storage.visible.find(params[:id]) + @storage = ::Storages::Storage.visible.find(params[:id]) end def prepare_storage_for_access_management_form diff --git a/modules/storages/app/models/storages/storage.rb b/modules/storages/app/models/storages/storage.rb index 4ba9cd3acf6..4fcfb544e60 100644 --- a/modules/storages/app/models/storages/storage.rb +++ b/modules/storages/app/models/storages/storage.rb @@ -57,7 +57,7 @@ module Storages validates :name, uniqueness: { case_sensitive: false } scope :visible, lambda { |user = User.current| - if user.allowed_in_any_project?(:manage_files_in_project) + if user.admin? || user.allowed_in_any_project?(:manage_files_in_project) all else where(project_storages: ProjectStorage.where(project: Project.allowed_to(user, :view_file_links))) diff --git a/modules/storages/app/views/storages/project_settings/edit.html.erb b/modules/storages/app/views/storages/project_settings/edit.html.erb index 8db2cfd68f8..a13486010eb 100644 --- a/modules/storages/app/views/storages/project_settings/edit.html.erb +++ b/modules/storages/app/views/storages/project_settings/edit.html.erb @@ -27,7 +27,7 @@ See COPYRIGHT and LICENSE files for more details. ++#%> -<% html_title t(:label_administration), t("project_module_storages"), t("label_edit_x", x: @object.storage.name) %> +<% html_title t(:label_administration), t("project_module_storages"), t("label_edit_x", x: @project_storage.storage.name) %> <%= render Primer::OpenProject::PageHeader.new do |header| From 3ca7dd5b5eba19aace33a5d4363bf04e17a029f6 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Mon, 9 Feb 2026 11:19:13 +0100 Subject: [PATCH 220/293] Fix loading project storages --- .../project_storage_members_controller.rb | 3 +- .../storages/project_storages_controller.rb | 4 +- .../view_project_storage_members_spec.rb | 154 +++++++++++------- 3 files changed, 97 insertions(+), 64 deletions(-) diff --git a/modules/storages/app/controllers/storages/project_settings/project_storage_members_controller.rb b/modules/storages/app/controllers/storages/project_settings/project_storage_members_controller.rb index 52bfc4f111c..8046bde4d0a 100644 --- a/modules/storages/app/controllers/storages/project_settings/project_storage_members_controller.rb +++ b/modules/storages/app/controllers/storages/project_settings/project_storage_members_controller.rb @@ -36,6 +36,7 @@ class Storages::ProjectSettings::ProjectStorageMembersController < Projects::Set menu_item :settings_project_storages + before_action :find_project_by_project_id before_action :find_project_storage, only: %i[index] def index @@ -53,7 +54,7 @@ class Storages::ProjectSettings::ProjectStorageMembersController < Projects::Set private def find_project_storage - @project_storage = Storages::ProjectStorage.find(params[:project_storage_id]) + @project_storage = Storages::ProjectStorage.where(project: @project).find(params[:project_storage_id]) @storage = @project_storage.storage end end diff --git a/modules/storages/app/controllers/storages/project_storages_controller.rb b/modules/storages/app/controllers/storages/project_storages_controller.rb index 686faec4ef6..eb03f7df471 100644 --- a/modules/storages/app/controllers/storages/project_storages_controller.rb +++ b/modules/storages/app/controllers/storages/project_storages_controller.rb @@ -34,8 +34,8 @@ class Storages::ProjectStoragesController < ApplicationController menu_item :overview before_action :require_login - before_action :find_project_stroage before_action :find_project_by_project_id + before_action :find_project_stroage before_action :render_403, unless: -> { User.current.allowed_in_project?(:view_file_links, @project) } no_authorization_required! :open @@ -121,7 +121,7 @@ class Storages::ProjectStoragesController < ApplicationController end def project_storage_scope - Storages::ProjectStorage.where(id: @project_storage.id) + Storages::ProjectStorage.where(project_id: @project.id, id: @project_storage.id) end def test_folder_access diff --git a/modules/storages/spec/features/view_project_storage_members_spec.rb b/modules/storages/spec/features/view_project_storage_members_spec.rb index 737e4904cb0..ceb6718fba6 100644 --- a/modules/storages/spec/features/view_project_storage_members_spec.rb +++ b/modules/storages/spec/features/view_project_storage_members_spec.rb @@ -60,36 +60,105 @@ RSpec.describe "Project storage members connection status view", :js do expect(page).to have_no_text("Members connection status") end - it "lists project members connection statuses" do - login_as user + context "when the user is not allowed to view members of the project" do + it "does not list any members" do + login_as user - # Go to Projects -> Settings -> File Storages - visit external_file_storages_project_settings_project_storages_path(project) + # Go to Projects -> Settings -> File Storages + visit external_file_storages_project_settings_project_storages_path(project) - expect(page).to have_title("Files") - expect(page).to have_text(storage.name) - page.find(".icon.icon-group").click + expect(page).to have_title("Files") + expect(page).to have_text(storage.name) + page.find(".icon.icon-group").click - # Members connection status page - expect(page).to have_current_path project_settings_project_storage_members_path(project_id: project, - project_storage_id: project_storage) + # Members connection status page + expected_current_path = project_settings_project_storage_members_path(project_id: project, + project_storage_id: project_storage) + expect(page).to have_current_path(expected_current_path) - aggregate_failures "Verifying Connection Statuses" do - [ - [user, "Not connected. The user should login to the storage via the following link."], - [admin_user, "Connected"], - [connected_user, "Connected"], - [connected_no_permissions_user, "User role has no storages permissions"], - [disconnected_user, "Not connected. The user should login to the storage via the following link."], - [disconnected_sso_user, "Not connected. The user should login to the storage via the following link."], - [group_user, "Not connected. The user should login to the storage via the following link."] - ].each do |(principal, status)| - expect(page).to have_css("#member-#{principal.id} .name", text: principal.name) - expect(page).to have_css("#member-#{principal.id} .status", text: status) + expect(page).to have_text("No members to display.") + end + end + + context "when the user is allowed to view members of the project" do + before do + role = create(:project_role, permissions: %i[view_members]) + existing_member = project.members.find_by(principal: user, entity: nil) + if existing_member + existing_member.roles << role + else + create(:member, principal: user, project: project, roles: [role]) end + end - [placeholder_user, group].each do |principal| - expect(page).to have_no_css("#member-#{principal.id} .name", text: principal.name) + it "lists project members connection statuses" do + login_as user + + # Go to Projects -> Settings -> File Storages + visit external_file_storages_project_settings_project_storages_path(project) + + expect(page).to have_title("Files") + expect(page).to have_text(storage.name) + page.find(".icon.icon-group").click + + # Members connection status page + expect(page).to have_current_path project_settings_project_storage_members_path(project_id: project, + project_storage_id: project_storage) + + aggregate_failures "Verifying Connection Statuses" do + [ + [user, "Not connected. The user should login to the storage via the following link."], + [admin_user, "Connected"], + [connected_user, "Connected"], + [connected_no_permissions_user, "User role has no storages permissions"], + [disconnected_user, "Not connected. The user should login to the storage via the following link."], + [disconnected_sso_user, "Not connected. The user should login to the storage via the following link."], + [group_user, "Not connected. The user should login to the storage via the following link."] + ].each do |(principal, status)| + expect(page).to have_css("#member-#{principal.id} .name", text: principal.name) + expect(page).to have_css("#member-#{principal.id} .status", text: status) + end + + [placeholder_user, group].each do |principal| + expect(page).to have_no_css("#member-#{principal.id} .name", text: principal.name) + end + end + end + + context "when the storage authenticates through SSO" do + let!(:storage) { create(:nextcloud_storage, :as_automatically_managed, :oidc_sso_enabled) } + + it "lists project members connection statuses" do + login_as user + + # Go to Projects -> Settings -> File Storages + visit external_file_storages_project_settings_project_storages_path(project) + + expect(page).to have_title("Files") + expect(page).to have_text(storage.name) + page.find(".icon.icon-group").click + + # Members connection status page + expect(page).to have_current_path project_settings_project_storage_members_path(project_id: project, + project_storage_id: project_storage) + + not_connectable = "Not connectable. The storage requires login through an SSO provider, " \ + "but the user is not logging in through SSO." + aggregate_failures "Verifying Connection Statuses" do + [ + [user, not_connectable], + [connected_user, "Connected"], + [disconnected_user, not_connectable], + [disconnected_sso_user, "Not yet connected, SSO should automatically connect them, once looking at files."] + ].each do |(principal, status)| + expect(page).to have_css("#member-#{principal.id} .name", text: principal.name) + expect(page).to have_css("#member-#{principal.id} .status", text: status) + end + + [placeholder_user, group].each do |principal| + expect(page).to have_no_css("#member-#{principal.id} .name", text: principal.name) + end + end end end end @@ -114,43 +183,6 @@ RSpec.describe "Project storage members connection status view", :js do expect(page).to have_text("No members to display.") end - context "when the storage authenticates through SSO" do - let!(:storage) { create(:nextcloud_storage, :as_automatically_managed, :oidc_sso_enabled) } - - it "lists project members connection statuses" do - login_as user - - # Go to Projects -> Settings -> File Storages - visit external_file_storages_project_settings_project_storages_path(project) - - expect(page).to have_title("Files") - expect(page).to have_text(storage.name) - page.find(".icon.icon-group").click - - # Members connection status page - expect(page).to have_current_path project_settings_project_storage_members_path(project_id: project, - project_storage_id: project_storage) - - not_connectable = "Not connectable. The storage requires login through an SSO provider, " \ - "but the user is not logging in through SSO." - aggregate_failures "Verifying Connection Statuses" do - [ - [user, not_connectable], - [connected_user, "Connected"], - [disconnected_user, not_connectable], - [disconnected_sso_user, "Not yet connected, SSO should automatically connect them, once looking at files."] - ].each do |(principal, status)| - expect(page).to have_css("#member-#{principal.id} .name", text: principal.name) - expect(page).to have_css("#member-#{principal.id} .status", text: status) - end - - [placeholder_user, group].each do |principal| - expect(page).to have_no_css("#member-#{principal.id} .name", text: principal.name) - end - end - end - end - def create_project_with_storage_and_members role_can_read_files = create(:project_role, permissions: %i[manage_files_in_project read_files]) role_cannot_read_files = create(:project_role, permissions: %i[manage_files_in_project]) From 2a934faeb9e65375a3b0b2a6b4ac11c39d480701 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Mon, 9 Feb 2026 11:52:40 +0100 Subject: [PATCH 221/293] Fix archiving controller --- app/controllers/projects/archive_controller.rb | 16 ++++++++++------ app/controllers/projects_controller.rb | 9 ++++++++- spec/controllers/projects_controller_spec.rb | 4 +--- spec/features/projects/lists/filters_spec.rb | 2 +- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/app/controllers/projects/archive_controller.rb b/app/controllers/projects/archive_controller.rb index 4f5fd317a3e..37b3e9dd191 100644 --- a/app/controllers/projects/archive_controller.rb +++ b/app/controllers/projects/archive_controller.rb @@ -31,7 +31,7 @@ class Projects::ArchiveController < ApplicationController include OpTurbo::ComponentStream - before_action :find_project_by_project_id + before_action :find_project_including_archived before_action :authorize, only: %i[create dialog] before_action :require_admin, only: [:destroy] @@ -49,17 +49,21 @@ class Projects::ArchiveController < ApplicationController private + def find_project_including_archived + # The visible scope filters out archived projects, but here we want to explicitly unarchive them. + # The contracts do proper permission checks, so we can skip the visible scope here. + @project = Project.find(params[:project_id]) + end + def change_status_action(status) service_call = change_status(status) - if service_call.success? - redirect_to(projects_path, status: :see_other) - else + if !service_call.success? flash[:error] = t(:"error_can_not_#{status}_project", errors: service_call.errors.full_messages.join(", ")) - redirect_back fallback_location: projects_path, - status: :see_other end + + redirect_to(projects_path, status: :see_other) end def change_status(status) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index bb62d32a288..374ff30d74a 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -34,7 +34,8 @@ class ProjectsController < ApplicationController menu_item :overview menu_item :roadmap, only: :roadmap - before_action :find_project, except: %i[index new create] + before_action :find_project, except: %i[index new create destroy destroy_info] + before_action :find_project_including_archived, only: %i[destroy destroy_info] before_action :load_query_or_deny_access, only: %i[index] before_action :authorize, only: %i[copy_form copy deactivate_work_package_attachments export_project_initiation_pdf] @@ -181,6 +182,12 @@ class ProjectsController < ApplicationController private + def find_project_including_archived + # The actions that use this method are only accessible to admins, so we can show them archived projects as well and + # can skip the visible scope here. + @project = Project.find(params[:id]) + end + def from_template? = @template.present? def new_blank diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 04c511faaeb..a4f7d0f13c2 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -478,9 +478,7 @@ RSpec.describe ProjectsController do let(:service_result) { ServiceResult.new(success:) } before do - visible_relation = instance_double(ActiveRecord::Relation) - allow(Project).to receive(:visible).and_return(visible_relation) - allow(visible_relation).to receive(:find).with(project.id.to_s).and_return(project) + allow(Project).to receive(:find).with(project.id.to_s).and_return(project) deletion_service = instance_double(Projects::ScheduleDeletionService, call: service_result) diff --git a/spec/features/projects/lists/filters_spec.rb b/spec/features/projects/lists/filters_spec.rb index bb73c6157c3..3c7904856a1 100644 --- a/spec/features/projects/lists/filters_spec.rb +++ b/spec/features/projects/lists/filters_spec.rb @@ -188,7 +188,7 @@ RSpec.describe "Projects list filters", :js, with_settings: { login_required?: f # The child project does not get unarchived automatically visit project_path(child_project) - expect(page).to have_text("The project you're trying to access has been archived.") + expect(page).to have_text("The page you were trying to access doesn't exist or has been removed.") visit project_path(parent_project) expect(page).to have_text(parent_project.name) From a8fc46d0551ff59ea3f82a9bb6fa021956c990cf Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 9 Feb 2026 11:19:59 -0300 Subject: [PATCH 222/293] Make burndown partial locals strict, prune unused --- .../app/views/rb_burndown_charts/_burndown.html.erb | 6 ++++-- modules/backlogs/app/views/rb_burndown_charts/show.html.erb | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/backlogs/app/views/rb_burndown_charts/_burndown.html.erb b/modules/backlogs/app/views/rb_burndown_charts/_burndown.html.erb index 487f17ba88a..41004c992b2 100644 --- a/modules/backlogs/app/views/rb_burndown_charts/_burndown.html.erb +++ b/modules/backlogs/app/views/rb_burndown_charts/_burndown.html.erb @@ -27,7 +27,9 @@ See COPYRIGHT and LICENSE files for more details. ++#%> +<%# locals: (burndown:) %> + <%= angular_component_tag "opce-burndown-chart", "chart-data": { - labels: xaxis_labels(@burndown), - datasets: dataseries(@burndown) + labels: xaxis_labels(burndown), + datasets: dataseries(burndown) }.to_json %> diff --git a/modules/backlogs/app/views/rb_burndown_charts/show.html.erb b/modules/backlogs/app/views/rb_burndown_charts/show.html.erb index 5d052463745..e00347c58e8 100644 --- a/modules/backlogs/app/views/rb_burndown_charts/show.html.erb +++ b/modules/backlogs/app/views/rb_burndown_charts/show.html.erb @@ -49,7 +49,7 @@ See COPYRIGHT and LICENSE files for more details. %> <% if @burndown %> - <%= render partial: "burndown", locals: { div: "burndown_", burndown: @burndown } %> + <%= render partial: "burndown", locals: { burndown: @burndown } %> <% else %> <%= render(Primer::Beta::Blankslate.new(border: true, spacious: true)) do |blankslate| From 8a30e637c87e233c170d437600b868a18e87070a Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Mon, 9 Feb 2026 15:26:41 +0100 Subject: [PATCH 223/293] Fix rubocop issues after refactoring --- app/controllers/forums_controller.rb | 2 +- app/controllers/members_controller.rb | 2 +- app/controllers/news_controller.rb | 2 +- .../backlogs/patches/versions_controller_patch.rb | 2 +- .../admin/storages/project_storages_controller.rb | 2 +- spec/controllers/repositories_controller_spec.rb | 11 +++++++---- 6 files changed, 12 insertions(+), 9 deletions(-) diff --git a/app/controllers/forums_controller.rb b/app/controllers/forums_controller.rb index 91fccd142af..9ff04248152 100644 --- a/app/controllers/forums_controller.rb +++ b/app/controllers/forums_controller.rb @@ -49,7 +49,7 @@ class ForumsController < ApplicationController :forums end - def show + def show # rubocop:disable Metrics/AbcSize sort_init "updated_at", "desc" sort_update "created_at" => "#{Message.table_name}.created_at", "replies" => "#{Message.table_name}.replies_count", diff --git a/app/controllers/members_controller.rb b/app/controllers/members_controller.rb index f5ace4e1872..4985c98cd45 100644 --- a/app/controllers/members_controller.rb +++ b/app/controllers/members_controller.rb @@ -39,7 +39,7 @@ class MembersController < ApplicationController set_index_data! end - def create + def create # rubocop:disable Metrics/AbcSize overall_result = [] find_or_create_users(send_notification: true) do |member_params| diff --git a/app/controllers/news_controller.rb b/app/controllers/news_controller.rb index 5bed0d7eca5..7e5a4baa8c4 100644 --- a/app/controllers/news_controller.rb +++ b/app/controllers/news_controller.rb @@ -40,7 +40,7 @@ class NewsController < ApplicationController accept_key_auth :index - def index + def index # rubocop:disable Metrics/AbcSize scope = @project ? @project.news : News.visible @news = scope.merge(News.latest_for(current_user, count: 0)) diff --git a/modules/backlogs/lib/open_project/backlogs/patches/versions_controller_patch.rb b/modules/backlogs/lib/open_project/backlogs/patches/versions_controller_patch.rb index 2898f4e0699..a3cc88e924d 100644 --- a/modules/backlogs/lib/open_project/backlogs/patches/versions_controller_patch.rb +++ b/modules/backlogs/lib/open_project/backlogs/patches/versions_controller_patch.rb @@ -27,7 +27,7 @@ #++ module OpenProject::Backlogs::Patches::VersionsControllerPatch - def self.included(base) + def self.included(base) # rubocop:disable Metrics/AbcSize base.class_eval do include VersionSettingsHelper diff --git a/modules/storages/app/controllers/storages/admin/storages/project_storages_controller.rb b/modules/storages/app/controllers/storages/admin/storages/project_storages_controller.rb index 50c79c03a28..cc4a459636a 100644 --- a/modules/storages/app/controllers/storages/admin/storages/project_storages_controller.rb +++ b/modules/storages/app/controllers/storages/admin/storages/project_storages_controller.rb @@ -154,7 +154,7 @@ class Storages::Admin::Storages::ProjectStoragesController < ApplicationControll respond_with_turbo_streams end - def find_projects_to_activate_for_storage + def find_projects_to_activate_for_storage # rubocop:disable Metrics/AbcSize if (project_ids = params.to_unsafe_h[:storages_project_storage][:project_ids]).present? @projects = Project.visible.find(project_ids) else diff --git a/spec/controllers/repositories_controller_spec.rb b/spec/controllers/repositories_controller_spec.rb index de51551472d..844ee2aab82 100644 --- a/spec/controllers/repositories_controller_spec.rb +++ b/spec/controllers/repositories_controller_spec.rb @@ -42,9 +42,12 @@ RSpec.describe RepositoriesController do scm_type: "local", url:, project:) - allow(repo).to receive(:default_branch).and_return("master") - allow(repo).to receive(:branches).and_return(["master"]) - allow(repo).to receive(:save).and_return(true) + + allow(repo).to receive_messages({ + default_branch: "master", + branches: ["master"], + save: true + }) repo end @@ -52,7 +55,7 @@ RSpec.describe RepositoriesController do before do login_as(user) - visible_relation = double("relation").as_null_object + visible_relation = instance_double(Project.all.class).as_null_object allow(Project).to receive(:visible).and_return(visible_relation) allow(visible_relation).to receive(:find).and_return(project) From 150e4bb7c5e68b36be0a603d57f875c0c7bee9bc Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Mon, 9 Feb 2026 15:27:25 +0100 Subject: [PATCH 224/293] Properly handle non visible user --- .../creation_wizard/create_artifact_work_package_service.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/services/projects/creation_wizard/create_artifact_work_package_service.rb b/app/services/projects/creation_wizard/create_artifact_work_package_service.rb index a065a9e98bc..c2ce205b038 100644 --- a/app/services/projects/creation_wizard/create_artifact_work_package_service.rb +++ b/app/services/projects/creation_wizard/create_artifact_work_package_service.rb @@ -182,7 +182,8 @@ module Projects::CreationWizard end def assignee_mention_tag - principal = Principal.visible.find(assigned_to_id) + principal = Principal.visible.find_by(id: assigned_to_id) + return "" if principal.nil? ApplicationController.helpers.content_tag( "mention", From 26125146884fcea66b41fc4c4b3a8f3e492e8c16 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Mon, 9 Feb 2026 16:25:43 +0200 Subject: [PATCH 225/293] Add spec for changing sprint on a story --- modules/backlogs/spec/features/stories_in_backlog_spec.rb | 7 +++++++ modules/backlogs/spec/support/pages/backlogs.rb | 2 ++ 2 files changed, 9 insertions(+) diff --git a/modules/backlogs/spec/features/stories_in_backlog_spec.rb b/modules/backlogs/spec/features/stories_in_backlog_spec.rb index 41e433b1191..85810a1b045 100644 --- a/modules/backlogs/spec/features/stories_in_backlog_spec.rb +++ b/modules/backlogs/spec/features/stories_in_backlog_spec.rb @@ -179,5 +179,12 @@ RSpec.describe "Stories in backlog", :js, .edit_story_in_details_view(sprint_story2, subject: "Updated story", story_points: 3) backlogs_page.expect_velocity(sprint, 8) + + # Assigning the story to the backlog will move it to the backlog + backlogs_page + .edit_story_in_details_view(sprint_story1, version: backlog) + + backlogs_page.expect_story_not_in_sprint(sprint_story1, sprint) + backlogs_page.expect_story_in_sprint(sprint_story1, backlog) end end diff --git a/modules/backlogs/spec/support/pages/backlogs.rb b/modules/backlogs/spec/support/pages/backlogs.rb index 0674fe8398d..2c58627050b 100644 --- a/modules/backlogs/spec/support/pages/backlogs.rb +++ b/modules/backlogs/spec/support/pages/backlogs.rb @@ -91,6 +91,8 @@ module Pages def edit_story_in_details_view(story, **attributes) click_in_story_menu(story, "Open details view") + wait_for { find("turbo-frame#content-bodyRight")[:complete] }.to be_truthy + expect(page).to have_current_path details_backlogs_project_backlogs_path(story.project, story) alter_attributes_in_details_view(story, **attributes) From d8c530da47bc29f18ceb851b14376be65bdc280a Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 9 Feb 2026 11:47:28 -0300 Subject: [PATCH 226/293] Add spec expectation for changing type --- .../spec/features/stories_in_backlog_spec.rb | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/modules/backlogs/spec/features/stories_in_backlog_spec.rb b/modules/backlogs/spec/features/stories_in_backlog_spec.rb index 85810a1b045..f7b70fa87af 100644 --- a/modules/backlogs/spec/features/stories_in_backlog_spec.rb +++ b/modules/backlogs/spec/features/stories_in_backlog_spec.rb @@ -31,8 +31,7 @@ require "spec_helper" require_relative "../support/pages/backlogs" -RSpec.describe "Stories in backlog", :js, - :selenium do +RSpec.describe "Stories in backlog", :js, :selenium, :settings_reset do let!(:project) do create(:project, types: [story, task, other_story], @@ -135,10 +134,11 @@ RSpec.describe "Stories in backlog", :js, before do login_as current_user - allow(Setting) - .to receive(:plugin_openproject_backlogs) - .and_return("story_types" => [story.id.to_s, other_story.id.to_s], - "task_type" => task.id.to_s) + + Setting.plugin_openproject_backlogs = { + "story_types" => [story.id.to_s, other_story.id.to_s], + "task_type" => task.id.to_s + } end it "displays stories which are editable via details view" do @@ -186,5 +186,11 @@ RSpec.describe "Stories in backlog", :js, backlogs_page.expect_story_not_in_sprint(sprint_story1, sprint) backlogs_page.expect_story_in_sprint(sprint_story1, backlog) + + # Changing type to TASK will remove the item from the list + backlogs_page + .edit_story_in_details_view(sprint_story2, type: task.name) + + backlogs_page.expect_story_not_in_sprint(sprint_story2, sprint) end end From cf8a307a9ce3f2b05a9d1c8f4872846c86c77b43 Mon Sep 17 00:00:00 2001 From: Wieland Lindenthal Date: Mon, 9 Feb 2026 17:40:51 +0100 Subject: [PATCH 227/293] Get Hocuspocus real time collaboration server into pullpreview (#20924) Integrate hocuspocus with pullpreview --- .github/workflows/pullpreview.yml | 4 +++- docker/pullpreview/Caddyfile | 5 +++++ docker/pullpreview/docker-compose.yml | 23 ++++++++++++++++++++++- 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 docker/pullpreview/Caddyfile diff --git a/.github/workflows/pullpreview.yml b/.github/workflows/pullpreview.yml index 63990de45fc..718e8448e90 100644 --- a/.github/workflows/pullpreview.yml +++ b/.github/workflows/pullpreview.yml @@ -44,6 +44,7 @@ jobs: - name: Prepare docker-compose files run: | cp ./docker/pullpreview/docker-compose.yml ./docker-compose.pullpreview.yml + cp ./docker/pullpreview/Caddyfile ./Caddyfile cp ./docker/prod/Dockerfile ./Dockerfile - uses: pullpreview/action@v6 with: @@ -54,7 +55,8 @@ jobs: instance_type: xlarge ports: 80,443,8080 default_port: 443 - ttl: 10d + ttl: 8d + dns: my.opf.run env: AWS_ACCESS_KEY_ID: "${{ secrets.AWS_ACCESS_KEY_ID }}" AWS_SECRET_ACCESS_KEY: "${{ secrets.AWS_SECRET_ACCESS_KEY }}" diff --git a/docker/pullpreview/Caddyfile b/docker/pullpreview/Caddyfile new file mode 100644 index 00000000000..3e9a2f1edaa --- /dev/null +++ b/docker/pullpreview/Caddyfile @@ -0,0 +1,5 @@ +{$PULLPREVIEW_PUBLIC_DNS} { + # Websocket proxy for hocuspocus + reverse_proxy /hocuspocus hocuspocus:1234 + reverse_proxy web:8080 +} \ No newline at end of file diff --git a/docker/pullpreview/docker-compose.yml b/docker/pullpreview/docker-compose.yml index 7be578a1adf..8bb3c2b6f04 100644 --- a/docker/pullpreview/docker-compose.yml +++ b/docker/pullpreview/docker-compose.yml @@ -23,6 +23,9 @@ x-defaults: &defaults environment: - "DATABASE_URL=postgresql://app:p4ssw0rd@db:5432/app?encoding=utf8&pool=5&timeout=5000&reconnect=true" - "OPENPROJECT_RAILS__CACHE__STORE=file_store" + - "OPENPROJECT_COLLABORATIVE__EDITING__HOCUSPOCUS__URL=wss://${PULLPREVIEW_PUBLIC_DNS}/hocuspocus" + - "OPENPROJECT_COLLABORATIVE__EDITING__HOCUSPOCUS__SECRET=secret12345" + - "OPENPROJECT_ADDITIONAL__HOSTS=web" - "RAILS_ENV=production" - "SECRET_KEY_BASE=d4e74f017910ac56c6ebad01165b7e1b37f4c9c02e9716836f8670cdc8d65a231e64e4f6416b19c8" networks: @@ -32,17 +35,21 @@ services: proxy: image: caddy:2 restart: unless-stopped - command: "caddy reverse-proxy --from '${PULLPREVIEW_URL}' --to web:8080" + command: "caddy run --config /etc/caddy/Caddyfile" networks: - frontend - backend depends_on: - web + - hocuspocus + environment: + - "PULLPREVIEW_PUBLIC_DNS=${PULLPREVIEW_PUBLIC_DNS}" ports: - "80:80" - "443:443" volumes: - "caddy_data:/data" + - "./Caddyfile:/etc/caddy/Caddyfile:ro" db: image: postgres:17 @@ -71,3 +78,17 @@ services: command: "./docker/prod/worker --seed --set attachment_max_size=262144,host_name=${PULLPREVIEW_PUBLIC_DNS}" depends_on: - db + + hocuspocus: + image: openproject/hocuspocus:latest + depends_on: + - web + networks: + - frontend + - backend + environment: + - NODE_TLS_REJECT_UNAUTHORIZED=0 + - SECRET=secret12345 + - OPENPROJECT_URL=http://web:8080 + expose: + - "1234" From ccbefa2f133b54261ab1a3720ef6fcc84b6830d3 Mon Sep 17 00:00:00 2001 From: Ivan Kuchin Date: Mon, 9 Feb 2026 18:55:18 +0100 Subject: [PATCH 228/293] use condition with EXISTS instead of confusingly ordered JOINs --- .../journals/create_service/customizable.rb | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/app/services/journals/create_service/customizable.rb b/app/services/journals/create_service/customizable.rb index 9ba8f39f5f3..649b3c777d7 100644 --- a/app/services/journals/create_service/customizable.rb +++ b/app/services/journals/create_service/customizable.rb @@ -56,9 +56,9 @@ class Journals::CreateService custom_values.custom_field_id, #{normalize_newlines_sql('custom_values.value')} FROM custom_values - #{availability_join} WHERE #{only_if_created_sql} + AND #{availability_condition} AND custom_values.customized_id = :journable_id AND custom_values.customized_type = :journable_class_name AND custom_values.value IS NOT NULL @@ -77,9 +77,9 @@ class Journals::CreateService ARRAY_AGG(#{normalize_newlines_sql('custom_values.value')} ORDER BY value) AS value FROM custom_values - #{availability_join} WHERE - custom_values.customized_id = :journable_id + #{availability_condition} + AND custom_values.customized_id = :journable_id AND custom_values.customized_type = :customized_type AND custom_values.value != '' GROUP BY @@ -105,16 +105,25 @@ class Journals::CreateService private - def availability_join - return "" unless journable.is_a?(Project) + def availability_condition + return "1 = 1" unless journable.is_a?(Project) <<~SQL # rubocop:disable Rails/SquishedSQLHeredocs - LEFT OUTER JOIN project_custom_field_project_mappings - ON project_custom_field_project_mappings.custom_field_id = custom_values.custom_field_id - AND project_custom_field_project_mappings.project_id = :journable_id - INNER JOIN custom_fields - ON custom_fields.id = custom_values.custom_field_id - AND (custom_fields.is_for_all = TRUE OR project_custom_field_project_mappings.project_id IS NOT NULL) + ( + EXISTS ( + SELECT 1 + FROM custom_fields + WHERE custom_fields.id = custom_values.custom_field_id + AND custom_fields.is_for_all = TRUE + ) + OR + EXISTS ( + SELECT 1 + FROM project_custom_field_project_mappings + WHERE project_custom_field_project_mappings.custom_field_id = custom_values.custom_field_id + AND project_custom_field_project_mappings.project_id = :journable_id + ) + ) SQL end end From fab500f6b9e5dff444643f3d06368adcc7e79c73 Mon Sep 17 00:00:00 2001 From: Ivan Kuchin Date: Mon, 9 Feb 2026 20:17:12 +0100 Subject: [PATCH 229/293] use one EXISTS on a JOIN instead of two separate EXISTS to reduce the cost --- .../journals/create_service/customizable.rb | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/app/services/journals/create_service/customizable.rb b/app/services/journals/create_service/customizable.rb index 649b3c777d7..5efb4cadf51 100644 --- a/app/services/journals/create_service/customizable.rb +++ b/app/services/journals/create_service/customizable.rb @@ -109,20 +109,14 @@ class Journals::CreateService return "1 = 1" unless journable.is_a?(Project) <<~SQL # rubocop:disable Rails/SquishedSQLHeredocs - ( - EXISTS ( - SELECT 1 - FROM custom_fields - WHERE custom_fields.id = custom_values.custom_field_id - AND custom_fields.is_for_all = TRUE - ) - OR - EXISTS ( - SELECT 1 - FROM project_custom_field_project_mappings - WHERE project_custom_field_project_mappings.custom_field_id = custom_values.custom_field_id + EXISTS ( + SELECT 1 + FROM custom_fields + LEFT JOIN project_custom_field_project_mappings + ON project_custom_field_project_mappings.custom_field_id = custom_fields.id AND project_custom_field_project_mappings.project_id = :journable_id - ) + WHERE custom_fields.id = custom_values.custom_field_id + AND (custom_fields.is_for_all = TRUE OR project_custom_field_project_mappings.project_id IS NOT NULL) ) SQL end From 2a41f1dd42e73d02864417ee2b87005d05780a0e Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 9 Feb 2026 16:30:20 -0300 Subject: [PATCH 230/293] Switch stories features to Cuprite --- modules/backlogs/spec/features/backlogs/create_story_spec.rb | 2 +- modules/backlogs/spec/features/stories_in_backlog_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/backlogs/spec/features/backlogs/create_story_spec.rb b/modules/backlogs/spec/features/backlogs/create_story_spec.rb index c2d8928e4f2..8563a3dd03b 100644 --- a/modules/backlogs/spec/features/backlogs/create_story_spec.rb +++ b/modules/backlogs/spec/features/backlogs/create_story_spec.rb @@ -29,7 +29,7 @@ require "spec_helper" require_relative "../../support/pages/backlogs" -RSpec.describe "Backlogs", :js, :selenium, driver: :firefox_de do # using FF due to regression #64158 +RSpec.describe "Backlogs", :js do let(:story_type) do create(:type_feature) end diff --git a/modules/backlogs/spec/features/stories_in_backlog_spec.rb b/modules/backlogs/spec/features/stories_in_backlog_spec.rb index f7b70fa87af..4d7658c4b08 100644 --- a/modules/backlogs/spec/features/stories_in_backlog_spec.rb +++ b/modules/backlogs/spec/features/stories_in_backlog_spec.rb @@ -31,7 +31,7 @@ require "spec_helper" require_relative "../support/pages/backlogs" -RSpec.describe "Stories in backlog", :js, :selenium, :settings_reset do +RSpec.describe "Stories in backlog", :js, :settings_reset do let!(:project) do create(:project, types: [story, task, other_story], From 25af4c0b38494b565e60645472e0008427801963 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 9 Feb 2026 16:41:51 -0300 Subject: [PATCH 231/293] Split stories in backlog feature into scenarios --- .../spec/features/stories_in_backlog_spec.rb | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/modules/backlogs/spec/features/stories_in_backlog_spec.rb b/modules/backlogs/spec/features/stories_in_backlog_spec.rb index 4d7658c4b08..c60aed2028b 100644 --- a/modules/backlogs/spec/features/stories_in_backlog_spec.rb +++ b/modules/backlogs/spec/features/stories_in_backlog_spec.rb @@ -133,19 +133,16 @@ RSpec.describe "Stories in backlog", :js, :settings_reset do let(:backlogs_page) { Pages::Backlogs.new(project) } before do - login_as current_user - Setting.plugin_openproject_backlogs = { "story_types" => [story.id.to_s, other_story.id.to_s], "task_type" => task.id.to_s } + + login_as current_user + backlogs_page.visit! end - it "displays stories which are editable via details view" do - backlogs_page.visit! - - # All stories are visible in their sprint/backlog - # but non stories are not displayed + it "displays stories in correct order, calculates velocity, and allows editing story points" do backlogs_page .expect_story_in_sprint(sprint_story1, sprint) @@ -179,15 +176,17 @@ RSpec.describe "Stories in backlog", :js, :settings_reset do .edit_story_in_details_view(sprint_story2, subject: "Updated story", story_points: 3) backlogs_page.expect_velocity(sprint, 8) + end - # Assigning the story to the backlog will move it to the backlog + it "moves story from sprint to backlog when version is changed via details view" do backlogs_page .edit_story_in_details_view(sprint_story1, version: backlog) backlogs_page.expect_story_not_in_sprint(sprint_story1, sprint) backlogs_page.expect_story_in_sprint(sprint_story1, backlog) + end - # Changing type to TASK will remove the item from the list + it "removes story from sprint when type is changed to non-story type via details view" do backlogs_page .edit_story_in_details_view(sprint_story2, type: task.name) From 1d6647fb51a4ac31b59342fbf42883c45d10a975 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 9 Feb 2026 16:54:21 -0300 Subject: [PATCH 232/293] Assert current path after details pane DOM update --- modules/backlogs/spec/support/pages/backlogs.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/backlogs/spec/support/pages/backlogs.rb b/modules/backlogs/spec/support/pages/backlogs.rb index 2c58627050b..a368f1ab6ea 100644 --- a/modules/backlogs/spec/support/pages/backlogs.rb +++ b/modules/backlogs/spec/support/pages/backlogs.rb @@ -91,10 +91,6 @@ module Pages def edit_story_in_details_view(story, **attributes) click_in_story_menu(story, "Open details view") - wait_for { find("turbo-frame#content-bodyRight")[:complete] }.to be_truthy - - expect(page).to have_current_path details_backlogs_project_backlogs_path(story.project, story) - alter_attributes_in_details_view(story, **attributes) end @@ -195,6 +191,8 @@ module Pages details_view.expect_tab :overview details_view.expect_subject + expect(page).to have_current_path details_backlogs_project_backlogs_path(story.project, story) + yield details_view end From 691d56b1777ac62185d5e3eee536e5645f3f6df4 Mon Sep 17 00:00:00 2001 From: OpenProject Actions CI Date: Tue, 10 Feb 2026 04:06:56 +0000 Subject: [PATCH 233/293] update locales from crowdin [ci skip] --- config/locales/crowdin/af.yml | 8 +- config/locales/crowdin/ar.yml | 8 +- config/locales/crowdin/az.yml | 8 +- config/locales/crowdin/be.yml | 8 +- config/locales/crowdin/bg.yml | 8 +- config/locales/crowdin/ca.yml | 8 +- config/locales/crowdin/ckb-IR.yml | 8 +- config/locales/crowdin/cs.yml | 8 +- config/locales/crowdin/da.yml | 8 +- config/locales/crowdin/de.yml | 26 +- config/locales/crowdin/el.yml | 8 +- config/locales/crowdin/eo.yml | 8 +- config/locales/crowdin/es.yml | 8 +- config/locales/crowdin/et.yml | 8 +- config/locales/crowdin/eu.yml | 8 +- config/locales/crowdin/fa.yml | 8 +- config/locales/crowdin/fi.yml | 8 +- config/locales/crowdin/fil.yml | 8 +- config/locales/crowdin/fr.yml | 8 +- config/locales/crowdin/he.yml | 8 +- config/locales/crowdin/hi.yml | 8 +- config/locales/crowdin/hr.yml | 8 +- config/locales/crowdin/hu.yml | 8 +- config/locales/crowdin/id.yml | 8 +- config/locales/crowdin/it.yml | 8 +- config/locales/crowdin/ja.yml | 8 +- config/locales/crowdin/js-tr.yml | 62 +-- config/locales/crowdin/ka.yml | 8 +- config/locales/crowdin/kk.yml | 8 +- config/locales/crowdin/ko.yml | 8 +- config/locales/crowdin/lt.yml | 8 +- config/locales/crowdin/lv.yml | 8 +- config/locales/crowdin/mn.yml | 8 +- config/locales/crowdin/ms.yml | 8 +- config/locales/crowdin/ne.yml | 8 +- config/locales/crowdin/nl.yml | 8 +- config/locales/crowdin/no.yml | 8 +- config/locales/crowdin/pl.yml | 8 +- config/locales/crowdin/pt-BR.yml | 8 +- config/locales/crowdin/pt-PT.yml | 8 +- config/locales/crowdin/ro.yml | 8 +- config/locales/crowdin/ru.yml | 40 +- config/locales/crowdin/rw.yml | 8 +- config/locales/crowdin/si.yml | 8 +- config/locales/crowdin/sk.yml | 8 +- config/locales/crowdin/sl.yml | 8 +- config/locales/crowdin/sr.yml | 8 +- config/locales/crowdin/sv.yml | 8 +- config/locales/crowdin/th.yml | 8 +- config/locales/crowdin/tr.yml | 150 +++--- config/locales/crowdin/uk.yml | 8 +- config/locales/crowdin/uz.yml | 8 +- config/locales/crowdin/vi.yml | 8 +- config/locales/crowdin/zh-CN.yml | 8 +- config/locales/crowdin/zh-TW.yml | 12 +- .../auth_saml/config/locales/crowdin/tr.yml | 78 +-- .../backlogs/config/locales/crowdin/af.yml | 117 ++--- .../backlogs/config/locales/crowdin/ar.yml | 125 ++--- .../backlogs/config/locales/crowdin/az.yml | 117 ++--- .../backlogs/config/locales/crowdin/be.yml | 121 ++--- .../backlogs/config/locales/crowdin/bg.yml | 117 ++--- .../backlogs/config/locales/crowdin/ca.yml | 117 ++--- .../config/locales/crowdin/ckb-IR.yml | 117 ++--- .../backlogs/config/locales/crowdin/cs.yml | 121 ++--- .../backlogs/config/locales/crowdin/da.yml | 117 ++--- .../backlogs/config/locales/crowdin/de.yml | 117 ++--- .../backlogs/config/locales/crowdin/el.yml | 117 ++--- .../backlogs/config/locales/crowdin/eo.yml | 117 ++--- .../backlogs/config/locales/crowdin/es.yml | 117 ++--- .../backlogs/config/locales/crowdin/et.yml | 117 ++--- .../backlogs/config/locales/crowdin/eu.yml | 117 ++--- .../backlogs/config/locales/crowdin/fa.yml | 117 ++--- .../backlogs/config/locales/crowdin/fi.yml | 117 ++--- .../backlogs/config/locales/crowdin/fil.yml | 117 ++--- .../backlogs/config/locales/crowdin/fr.yml | 117 ++--- .../backlogs/config/locales/crowdin/he.yml | 121 ++--- .../backlogs/config/locales/crowdin/hi.yml | 117 ++--- .../backlogs/config/locales/crowdin/hr.yml | 119 ++--- .../backlogs/config/locales/crowdin/hu.yml | 117 ++--- .../backlogs/config/locales/crowdin/id.yml | 115 ++--- .../backlogs/config/locales/crowdin/it.yml | 117 ++--- .../backlogs/config/locales/crowdin/ja.yml | 115 ++--- .../backlogs/config/locales/crowdin/js-af.yml | 3 + .../backlogs/config/locales/crowdin/js-ar.yml | 3 + .../backlogs/config/locales/crowdin/js-az.yml | 3 + .../backlogs/config/locales/crowdin/js-be.yml | 3 + .../backlogs/config/locales/crowdin/js-bg.yml | 3 + .../backlogs/config/locales/crowdin/js-ca.yml | 3 + .../config/locales/crowdin/js-ckb-IR.yml | 3 + .../backlogs/config/locales/crowdin/js-cs.yml | 3 + .../backlogs/config/locales/crowdin/js-da.yml | 3 + .../backlogs/config/locales/crowdin/js-de.yml | 3 + .../backlogs/config/locales/crowdin/js-el.yml | 3 + .../backlogs/config/locales/crowdin/js-eo.yml | 3 + .../backlogs/config/locales/crowdin/js-es.yml | 3 + .../backlogs/config/locales/crowdin/js-et.yml | 3 + .../backlogs/config/locales/crowdin/js-eu.yml | 3 + .../backlogs/config/locales/crowdin/js-fa.yml | 3 + .../backlogs/config/locales/crowdin/js-fi.yml | 3 + .../config/locales/crowdin/js-fil.yml | 3 + .../backlogs/config/locales/crowdin/js-fr.yml | 3 + .../backlogs/config/locales/crowdin/js-he.yml | 3 + .../backlogs/config/locales/crowdin/js-hi.yml | 3 + .../backlogs/config/locales/crowdin/js-hr.yml | 3 + .../backlogs/config/locales/crowdin/js-hu.yml | 3 + .../backlogs/config/locales/crowdin/js-id.yml | 3 + .../backlogs/config/locales/crowdin/js-it.yml | 3 + .../backlogs/config/locales/crowdin/js-ja.yml | 3 + .../backlogs/config/locales/crowdin/js-ka.yml | 3 + .../backlogs/config/locales/crowdin/js-kk.yml | 3 + .../backlogs/config/locales/crowdin/js-ko.yml | 3 + .../backlogs/config/locales/crowdin/js-lt.yml | 3 + .../backlogs/config/locales/crowdin/js-lv.yml | 3 + .../backlogs/config/locales/crowdin/js-mn.yml | 3 + .../backlogs/config/locales/crowdin/js-ms.yml | 3 + .../backlogs/config/locales/crowdin/js-ne.yml | 3 + .../backlogs/config/locales/crowdin/js-nl.yml | 3 + .../backlogs/config/locales/crowdin/js-no.yml | 3 + .../backlogs/config/locales/crowdin/js-pl.yml | 3 + .../config/locales/crowdin/js-pt-BR.yml | 3 + .../config/locales/crowdin/js-pt-PT.yml | 3 + .../backlogs/config/locales/crowdin/js-ro.yml | 3 + .../backlogs/config/locales/crowdin/js-ru.yml | 3 + .../backlogs/config/locales/crowdin/js-rw.yml | 3 + .../backlogs/config/locales/crowdin/js-si.yml | 3 + .../backlogs/config/locales/crowdin/js-sk.yml | 3 + .../backlogs/config/locales/crowdin/js-sl.yml | 3 + .../backlogs/config/locales/crowdin/js-sr.yml | 3 + .../backlogs/config/locales/crowdin/js-sv.yml | 3 + .../backlogs/config/locales/crowdin/js-th.yml | 3 + .../backlogs/config/locales/crowdin/js-tr.yml | 3 + .../backlogs/config/locales/crowdin/js-uk.yml | 3 + .../backlogs/config/locales/crowdin/js-uz.yml | 3 + .../backlogs/config/locales/crowdin/js-vi.yml | 3 + .../config/locales/crowdin/js-zh-CN.yml | 3 + .../config/locales/crowdin/js-zh-TW.yml | 3 + .../backlogs/config/locales/crowdin/ka.yml | 117 ++--- .../backlogs/config/locales/crowdin/kk.yml | 117 ++--- .../backlogs/config/locales/crowdin/ko.yml | 115 ++--- .../backlogs/config/locales/crowdin/lt.yml | 121 ++--- .../backlogs/config/locales/crowdin/lv.yml | 119 ++--- .../backlogs/config/locales/crowdin/mn.yml | 117 ++--- .../backlogs/config/locales/crowdin/ms.yml | 115 ++--- .../backlogs/config/locales/crowdin/ne.yml | 117 ++--- .../backlogs/config/locales/crowdin/nl.yml | 117 ++--- .../backlogs/config/locales/crowdin/no.yml | 117 ++--- .../backlogs/config/locales/crowdin/pl.yml | 121 ++--- .../backlogs/config/locales/crowdin/pt-BR.yml | 117 ++--- .../backlogs/config/locales/crowdin/pt-PT.yml | 117 ++--- .../backlogs/config/locales/crowdin/ro.yml | 119 ++--- .../backlogs/config/locales/crowdin/ru.yml | 121 ++--- .../backlogs/config/locales/crowdin/rw.yml | 117 ++--- .../backlogs/config/locales/crowdin/si.yml | 117 ++--- .../backlogs/config/locales/crowdin/sk.yml | 121 ++--- .../backlogs/config/locales/crowdin/sl.yml | 121 ++--- .../backlogs/config/locales/crowdin/sr.yml | 119 ++--- .../backlogs/config/locales/crowdin/sv.yml | 117 ++--- .../backlogs/config/locales/crowdin/th.yml | 115 ++--- .../backlogs/config/locales/crowdin/tr.yml | 121 ++--- .../backlogs/config/locales/crowdin/uk.yml | 121 ++--- .../backlogs/config/locales/crowdin/uz.yml | 117 ++--- .../backlogs/config/locales/crowdin/vi.yml | 115 ++--- .../backlogs/config/locales/crowdin/zh-CN.yml | 115 ++--- .../backlogs/config/locales/crowdin/zh-TW.yml | 115 ++--- .../bim/config/locales/crowdin/tr.seeders.yml | 468 +++++++++--------- modules/costs/config/locales/crowdin/tr.yml | 2 +- .../config/locales/crowdin/tr.seeders.yml | 10 +- .../documents/config/locales/crowdin/tr.yml | 118 ++--- .../grids/config/locales/crowdin/js-tr.yml | 6 +- modules/grids/config/locales/crowdin/tr.yml | 10 +- modules/meeting/config/locales/crowdin/af.yml | 4 +- modules/meeting/config/locales/crowdin/ar.yml | 4 +- modules/meeting/config/locales/crowdin/az.yml | 4 +- modules/meeting/config/locales/crowdin/be.yml | 4 +- modules/meeting/config/locales/crowdin/bg.yml | 4 +- modules/meeting/config/locales/crowdin/ca.yml | 4 +- .../meeting/config/locales/crowdin/ckb-IR.yml | 4 +- modules/meeting/config/locales/crowdin/cs.yml | 4 +- modules/meeting/config/locales/crowdin/da.yml | 4 +- modules/meeting/config/locales/crowdin/de.yml | 4 +- modules/meeting/config/locales/crowdin/el.yml | 4 +- modules/meeting/config/locales/crowdin/eo.yml | 4 +- modules/meeting/config/locales/crowdin/es.yml | 4 +- modules/meeting/config/locales/crowdin/et.yml | 4 +- modules/meeting/config/locales/crowdin/eu.yml | 4 +- modules/meeting/config/locales/crowdin/fa.yml | 4 +- modules/meeting/config/locales/crowdin/fi.yml | 4 +- .../meeting/config/locales/crowdin/fil.yml | 4 +- modules/meeting/config/locales/crowdin/fr.yml | 4 +- modules/meeting/config/locales/crowdin/he.yml | 4 +- modules/meeting/config/locales/crowdin/hi.yml | 4 +- modules/meeting/config/locales/crowdin/hr.yml | 4 +- modules/meeting/config/locales/crowdin/hu.yml | 4 +- modules/meeting/config/locales/crowdin/id.yml | 4 +- modules/meeting/config/locales/crowdin/it.yml | 4 +- modules/meeting/config/locales/crowdin/ja.yml | 4 +- modules/meeting/config/locales/crowdin/ka.yml | 4 +- modules/meeting/config/locales/crowdin/kk.yml | 4 +- modules/meeting/config/locales/crowdin/ko.yml | 4 +- modules/meeting/config/locales/crowdin/lt.yml | 4 +- modules/meeting/config/locales/crowdin/lv.yml | 4 +- modules/meeting/config/locales/crowdin/mn.yml | 4 +- modules/meeting/config/locales/crowdin/ms.yml | 4 +- modules/meeting/config/locales/crowdin/ne.yml | 4 +- modules/meeting/config/locales/crowdin/nl.yml | 4 +- modules/meeting/config/locales/crowdin/no.yml | 4 +- modules/meeting/config/locales/crowdin/pl.yml | 4 +- .../meeting/config/locales/crowdin/pt-BR.yml | 4 +- .../meeting/config/locales/crowdin/pt-PT.yml | 4 +- modules/meeting/config/locales/crowdin/ro.yml | 4 +- modules/meeting/config/locales/crowdin/ru.yml | 10 +- modules/meeting/config/locales/crowdin/rw.yml | 4 +- modules/meeting/config/locales/crowdin/si.yml | 4 +- modules/meeting/config/locales/crowdin/sk.yml | 4 +- modules/meeting/config/locales/crowdin/sl.yml | 4 +- modules/meeting/config/locales/crowdin/sr.yml | 4 +- modules/meeting/config/locales/crowdin/sv.yml | 4 +- modules/meeting/config/locales/crowdin/th.yml | 4 +- modules/meeting/config/locales/crowdin/tr.yml | 184 +++---- modules/meeting/config/locales/crowdin/uk.yml | 4 +- modules/meeting/config/locales/crowdin/uz.yml | 4 +- modules/meeting/config/locales/crowdin/vi.yml | 4 +- .../meeting/config/locales/crowdin/zh-CN.yml | 4 +- .../meeting/config/locales/crowdin/zh-TW.yml | 4 +- .../config/locales/crowdin/tr.yml | 76 +-- .../storages/config/locales/crowdin/ja.yml | 2 +- .../storages/config/locales/crowdin/tr.yml | 34 +- .../config/locales/crowdin/js-tr.yml | 2 +- .../config/locales/crowdin/tr.yml | 2 +- .../webhooks/config/locales/crowdin/tr.yml | 6 +- 230 files changed, 3691 insertions(+), 4735 deletions(-) diff --git a/config/locales/crowdin/af.yml b/config/locales/crowdin/af.yml index 406caa647b5..3c6b45f8ae5 100644 --- a/config/locales/crowdin/af.yml +++ b/config/locales/crowdin/af.yml @@ -113,7 +113,7 @@ af: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ af: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -3972,6 +3972,7 @@ af: notice_successful_delete: "Successful deletion." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Successful update." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4317,6 +4318,9 @@ af: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/ar.yml b/config/locales/crowdin/ar.yml index f501004da01..8eb09a62ec1 100644 --- a/config/locales/crowdin/ar.yml +++ b/config/locales/crowdin/ar.yml @@ -113,7 +113,7 @@ ar: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ ar: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -4172,6 +4172,7 @@ ar: notice_successful_delete: "حذف ناجح." notice_successful_cancel: "Successful cancellation." notice_successful_update: "تحديث ناجح." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4519,6 +4520,9 @@ ar: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/az.yml b/config/locales/crowdin/az.yml index c7c65f50310..cec69d8d0cc 100644 --- a/config/locales/crowdin/az.yml +++ b/config/locales/crowdin/az.yml @@ -113,7 +113,7 @@ az: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ az: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -3972,6 +3972,7 @@ az: notice_successful_delete: "Successful deletion." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Successful update." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4317,6 +4318,9 @@ az: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/be.yml b/config/locales/crowdin/be.yml index 3b68619743d..4a6959a0ab5 100644 --- a/config/locales/crowdin/be.yml +++ b/config/locales/crowdin/be.yml @@ -113,7 +113,7 @@ be: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ be: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -4072,6 +4072,7 @@ be: notice_successful_delete: "Successful deletion." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Successful update." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4419,6 +4420,9 @@ be: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/bg.yml b/config/locales/crowdin/bg.yml index d9c05e9dcda..8f569acf5a3 100644 --- a/config/locales/crowdin/bg.yml +++ b/config/locales/crowdin/bg.yml @@ -113,7 +113,7 @@ bg: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ bg: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -3972,6 +3972,7 @@ bg: notice_successful_delete: "Успешно изтриване." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Успешно обновяване." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4317,6 +4318,9 @@ bg: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/ca.yml b/config/locales/crowdin/ca.yml index dfedfd734a4..51bc1afed65 100644 --- a/config/locales/crowdin/ca.yml +++ b/config/locales/crowdin/ca.yml @@ -113,7 +113,7 @@ ca: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ ca: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -3966,6 +3966,7 @@ ca: notice_successful_delete: "Esborrat correctament." notice_successful_cancel: "Successful cancellation." notice_successful_update: "S'ha modificat correctament." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4308,6 +4309,9 @@ ca: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/ckb-IR.yml b/config/locales/crowdin/ckb-IR.yml index 711d7748c62..993df024c5a 100644 --- a/config/locales/crowdin/ckb-IR.yml +++ b/config/locales/crowdin/ckb-IR.yml @@ -113,7 +113,7 @@ ckb-IR: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ ckb-IR: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -3972,6 +3972,7 @@ ckb-IR: notice_successful_delete: "Successful deletion." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Successful update." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4317,6 +4318,9 @@ ckb-IR: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/cs.yml b/config/locales/crowdin/cs.yml index 16da218dce5..6e5311a4174 100644 --- a/config/locales/crowdin/cs.yml +++ b/config/locales/crowdin/cs.yml @@ -113,7 +113,7 @@ cs: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ cs: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -4071,6 +4071,7 @@ cs: notice_successful_delete: "Úspěšné odstranění." notice_successful_cancel: "Úspěšné zrušení." notice_successful_update: "Úspěšná aktualizace." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4418,6 +4419,9 @@ cs: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/da.yml b/config/locales/crowdin/da.yml index 7e1b0eec248..76c63ac1c1f 100644 --- a/config/locales/crowdin/da.yml +++ b/config/locales/crowdin/da.yml @@ -113,7 +113,7 @@ da: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ da: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -3970,6 +3970,7 @@ da: notice_successful_delete: "Sletning gennemført." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Opdatering gennemført." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4313,6 +4314,9 @@ da: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/de.yml b/config/locales/crowdin/de.yml index cc20f7f08e2..aa2725f3940 100644 --- a/config/locales/crowdin/de.yml +++ b/config/locales/crowdin/de.yml @@ -111,21 +111,21 @@ de: link: "Webhook" mcp_configurations: index: - description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." - resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." - resources_submit: "Update resources" + description: "Das Model Context Protocol ermöglicht es KI-Agenten, ihren Nutzern Tools und Ressourcen bereitzustellen, die diese OpenProject-Instanz zur Verfügung stellt." + resources_heading: "Ressourcen" + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_submit: "Ressourcen aktualisieren" tools_heading: "Tools" - tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." - tools_submit: "Update tools" + tools_description: "OpenProject stellt die folgenden Tools bereit. Jedes Tool kann nach Wunsch aktiviert, umbenannt und beschrieben werden. Weitere Informationen finden sich in der [Dokumentation zu MCP-Ressourcen](docs_url)." + tools_submit: "Tools aktualisieren" multi_update: - success: "MCP configurations were updated successfully." + success: "MCP-Konfigurationen wurden erfolgreich aktualisiert." server_form: - description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + description_caption: "Wie der MCP-Server gegenüber anderen Anwendungen beschrieben wird, die sich damit verbinden." + title_caption: "A short title shown to applications that connect to the MCP server." update: - failure: "MCP configuration could not be updated." - success: "MCP configuration was updated successfully." + failure: "Die MCP-Konfiguration konnte nicht aktualisiert werden." + success: "Die MCP-Konfiguration wurde erfolgreich aktualisiert." scim_clients: authentication_methods: sso: "JWT vom Identitätsanbieter" @@ -3964,6 +3964,7 @@ de: notice_successful_delete: "Erfolgreich gelöscht." notice_successful_cancel: "Erfolgreiche Absage." notice_successful_update: "Erfolgreich aktualisiert." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Erstellung fehlgeschlagen." notice_unsuccessful_create_with_reason: "Erstellung fehlgeschlagen: %{reason}" notice_unsuccessful_update: "Aktualisierung fehlgeschlagen." @@ -4309,6 +4310,9 @@ de: setting_capture_external_links: "Externe Links abfangen" setting_capture_external_links_text: > Wenn diese Funktion aktiviert ist, werden alle externen Links in formatiertem Text auf eine vor dem Link warnende Seite in der Applikation umgeleitet, bevor sie die Anwendung verlassen. Dies hilft, Benutzer vor potenziell bösartigen externen Websites zu schützen. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "Weiterleitung nach erster Anmeldung" setting_after_first_login_redirect_url_text_html: > Legen Sie einen Pfad fest, an den Nutzer:innen nach der ersten Anmeldung weitergeleitet werden. Wenn leer, führt er auf die Startseite des Onboarding-Tours.
    Beispiel: /meine/seite diff --git a/config/locales/crowdin/el.yml b/config/locales/crowdin/el.yml index 86cf70779fd..729d601637e 100644 --- a/config/locales/crowdin/el.yml +++ b/config/locales/crowdin/el.yml @@ -113,7 +113,7 @@ el: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ el: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -3967,6 +3967,7 @@ el: notice_successful_delete: "Επιτυχής διαγραφή." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Επιτυχής ενημέρωση." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4312,6 +4313,9 @@ el: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/eo.yml b/config/locales/crowdin/eo.yml index 9596491ae52..4f14624e04d 100644 --- a/config/locales/crowdin/eo.yml +++ b/config/locales/crowdin/eo.yml @@ -113,7 +113,7 @@ eo: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ eo: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -3972,6 +3972,7 @@ eo: notice_successful_delete: "Successful deletion." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Successful update." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4317,6 +4318,9 @@ eo: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/es.yml b/config/locales/crowdin/es.yml index aa0eb5b4cc1..5153a13aa2e 100644 --- a/config/locales/crowdin/es.yml +++ b/config/locales/crowdin/es.yml @@ -113,7 +113,7 @@ es: index: description: "El protocolo de contexto de modelo (MCP, por sus siglas en inglés) permite a los agentes de IA proporcionar a sus usuarios herramientas y recursos expuestos por esta instancia de OpenProject." resources_heading: "Recursos" - resources_description: "OpenProject implementa las siguientes herramientas. Cada una de ellas puede habilitarse, renombrarse y describirse como usted desee. Para más información, consulte la [documentación sobre recursos del MCP](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Actualizar recursos" tools_heading: "Herramientas" tools_description: "OpenProject implementa las siguientes herramientas. Cada una de ellas puede habilitarse, renombrarse y describirse como usted desee. Para más información, consulte la [documentación sobre herramientas del MCP](docs_url)." @@ -122,7 +122,7 @@ es: success: "Las configuraciones del MCP se actualizaron correctamente." server_form: description_caption: "Cómo se describirá el servidor del MCP a otras aplicaciones que se conecten a él." - title_caption: "Un título corto que se muestra a las aplicaciones que se conectan al servidor del MCP." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "No se ha podido actualizar la configuración del MCP." success: "La configuración del MCP se ha actualizado correctamente." @@ -3969,6 +3969,7 @@ es: notice_successful_delete: "Eliminado con éxito." notice_successful_cancel: "Cancelación exitosa." notice_successful_update: "Actualización correcta." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Error en la creación." notice_unsuccessful_create_with_reason: "Error en la creación: %{reason}" notice_unsuccessful_update: "Error al actualizar." @@ -4313,6 +4314,9 @@ es: setting_capture_external_links: "Detección de enlaces externos" setting_capture_external_links_text: > Cuando esta función está habilitada, todos los enlaces externos en texto formateado redirigirán a una página de advertencia antes de salir de la aplicación. Esto ayuda a proteger a los usuarios de sitios web externos potencialmente maliciosos. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "Primera redirección de inicio de sesión" setting_after_first_login_redirect_url_text_html: > Establezca una ruta para redirigir a los usuarios tras su primer inicio de sesión. Si está vacía, redirige a la página de inicio del recorrido de incorporación.
    Ejemplo: /my/page diff --git a/config/locales/crowdin/et.yml b/config/locales/crowdin/et.yml index 78e9a33e5cb..5ccff4287cc 100644 --- a/config/locales/crowdin/et.yml +++ b/config/locales/crowdin/et.yml @@ -113,7 +113,7 @@ et: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ et: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -3972,6 +3972,7 @@ et: notice_successful_delete: "Kustutamine õnnestus." notice_successful_cancel: "Tühistatud." notice_successful_update: "Uuendamine õnnestus." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Uuendamine ebaõnnestus." @@ -4317,6 +4318,9 @@ et: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/eu.yml b/config/locales/crowdin/eu.yml index 6c81dd5ef46..715cdbfeefa 100644 --- a/config/locales/crowdin/eu.yml +++ b/config/locales/crowdin/eu.yml @@ -113,7 +113,7 @@ eu: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ eu: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -3972,6 +3972,7 @@ eu: notice_successful_delete: "Successful deletion." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Successful update." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4317,6 +4318,9 @@ eu: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/fa.yml b/config/locales/crowdin/fa.yml index 42193f7ccf5..570e2b041ef 100644 --- a/config/locales/crowdin/fa.yml +++ b/config/locales/crowdin/fa.yml @@ -113,7 +113,7 @@ fa: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ fa: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -3972,6 +3972,7 @@ fa: notice_successful_delete: "Successful deletion." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Successful update." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4317,6 +4318,9 @@ fa: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/fi.yml b/config/locales/crowdin/fi.yml index 64e3f85f7ff..b6f0a735a2a 100644 --- a/config/locales/crowdin/fi.yml +++ b/config/locales/crowdin/fi.yml @@ -113,7 +113,7 @@ fi: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ fi: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -3972,6 +3972,7 @@ fi: notice_successful_delete: "Poisto onnistui." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Päivitys onnistui." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4317,6 +4318,9 @@ fi: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/fil.yml b/config/locales/crowdin/fil.yml index 2d1ffd1df92..42b53e050ff 100644 --- a/config/locales/crowdin/fil.yml +++ b/config/locales/crowdin/fil.yml @@ -113,7 +113,7 @@ fil: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ fil: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -3972,6 +3972,7 @@ fil: notice_successful_delete: "Matagumpay ang pagtanggal." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Matagumpay nai-update." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4315,6 +4316,9 @@ fil: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/fr.yml b/config/locales/crowdin/fr.yml index 96356eaf062..93665f731d4 100644 --- a/config/locales/crowdin/fr.yml +++ b/config/locales/crowdin/fr.yml @@ -113,7 +113,7 @@ fr: index: description: "Le protocole de contexte de modèle permet aux agents d'intelligence artificielle de fournir à leurs utilisateurs les outils et les ressources exposés par cette instance d'OpenProject." resources_heading: "Ressources" - resources_description: "OpenProject implémente les outils suivants. Chacun d'entre eux peut être activé, renommé et décrit comme vous le souhaitez. Pour en savoir plus, veuillez vous référer à la [documentation sur les ressources MCP](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Mettre à jour les ressources" tools_heading: "Outils" tools_description: "OpenProject implémente les outils suivants. Chacun d'entre eux peut être activé, renommé et décrit comme vous le souhaitez. Pour en savoir plus, veuillez vous référer à la [documentation sur les outils MCP](docs_url)." @@ -122,7 +122,7 @@ fr: success: "Les configurations MCP ont été mises à jour avec succès." server_form: description_caption: "Comment le serveur MCP sera décrit aux autres applications qui s'y connectent." - title_caption: "Un titre court affiché aux applications qui se connectent au serveur MCP." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "La configuration MCP n'a pas pu être mise à jour." success: "La configuration MCP a été mise à jour avec succès." @@ -3970,6 +3970,7 @@ fr: notice_successful_delete: "Suppression réussie." notice_successful_cancel: "Annulation réussie." notice_successful_update: "Mise à jour réussie." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Échec de la création." notice_unsuccessful_create_with_reason: "Échec de la création : %{reason}" notice_unsuccessful_update: "Mise à jour échouée." @@ -4315,6 +4316,9 @@ fr: setting_capture_external_links: "Saisir les liens externes" setting_capture_external_links_text: > Lorsque cette option est activée, tous les liens externes en texte formaté sont redirigés vers une page d'avertissement avant de quitter l'application. Cela permet de protéger les utilisateurs contre les sites web externes potentiellement malveillants. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "Redirection de première connexion" setting_after_first_login_redirect_url_text_html: > Définissez un chemin pour rediriger les utilisateurs après leur première connexion. S’il est vide, les utilisateurs seront redirigés vers la page d'accueil de la visite d'intégration.
    Exemple : /my/page diff --git a/config/locales/crowdin/he.yml b/config/locales/crowdin/he.yml index 5363a129dd1..b345305c6f7 100644 --- a/config/locales/crowdin/he.yml +++ b/config/locales/crowdin/he.yml @@ -113,7 +113,7 @@ he: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ he: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -4072,6 +4072,7 @@ he: notice_successful_delete: "Successful deletion." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Successful update." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4419,6 +4420,9 @@ he: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/hi.yml b/config/locales/crowdin/hi.yml index 77d2b7e5eda..db51a05b0d0 100644 --- a/config/locales/crowdin/hi.yml +++ b/config/locales/crowdin/hi.yml @@ -113,7 +113,7 @@ hi: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ hi: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -3970,6 +3970,7 @@ hi: notice_successful_delete: "Successful deletion." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Successful update." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4315,6 +4316,9 @@ hi: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/hr.yml b/config/locales/crowdin/hr.yml index 7e407ee0f7a..0b8be38cd98 100644 --- a/config/locales/crowdin/hr.yml +++ b/config/locales/crowdin/hr.yml @@ -113,7 +113,7 @@ hr: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ hr: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -4022,6 +4022,7 @@ hr: notice_successful_delete: "Brisanje uspješno." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Ažuriranje je uspješno završeno." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4368,6 +4369,9 @@ hr: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/hu.yml b/config/locales/crowdin/hu.yml index f3ea3ac2e9a..8063218466a 100644 --- a/config/locales/crowdin/hu.yml +++ b/config/locales/crowdin/hu.yml @@ -113,7 +113,7 @@ hu: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ hu: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -3970,6 +3970,7 @@ hu: notice_successful_delete: "Sikeres törlés." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Sikeres frissítés." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4315,6 +4316,9 @@ hu: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/id.yml b/config/locales/crowdin/id.yml index 45a41550f8f..7e71a58b3a0 100644 --- a/config/locales/crowdin/id.yml +++ b/config/locales/crowdin/id.yml @@ -113,7 +113,7 @@ id: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ id: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -3918,6 +3918,7 @@ id: notice_successful_delete: "Berhasil dihapus." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Berhasil diperbarui." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4259,6 +4260,9 @@ id: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/it.yml b/config/locales/crowdin/it.yml index c01080f23db..3ad97cf0cdc 100644 --- a/config/locales/crowdin/it.yml +++ b/config/locales/crowdin/it.yml @@ -113,7 +113,7 @@ it: index: description: "Il protocollo di contesto del modello consente agli agenti di intelligenza artificiale di fornire ai propri utenti strumenti e risorse esposti da questa istanza di OpenProject." resources_heading: "Risorse" - resources_description: "OpenProject implementa i seguenti strumenti. Ognuno di essi può essere abilitato, rinominato e descritto a piacere. Per ulteriori informazioni, consultare la [documentazione sulle risorse MCP](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Aggiorna risorse" tools_heading: "Strumenti" tools_description: "OpenProject implementa i seguenti strumenti. Ognuno di essi può essere abilitato, rinominato e descritto a piacere. Per ulteriori informazioni, consultare la [documentazione sugli strumenti MCP](docs_url)." @@ -122,7 +122,7 @@ it: success: "Configurazioni MCP aggiornate con successo." server_form: description_caption: "Come verrà descritto il server MCP alle altre applicazioni che vi si collegheranno." - title_caption: "Un breve titolo mostrato alle applicazioni che si connettono al server MCP." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "La configurazione MCP non può essere aggiornata." success: "La configurazione MCP è stata aggiornata correttamente." @@ -3969,6 +3969,7 @@ it: notice_successful_delete: "Cancellato con successo." notice_successful_cancel: "Cancellato con successo." notice_successful_update: "Aggiornato con successo." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creazione fallita." notice_unsuccessful_create_with_reason: "Creazione non riuscita: %{reason}" notice_unsuccessful_update: "Aggiornamento non riuscito." @@ -4314,6 +4315,9 @@ it: setting_capture_external_links: "Intercetta link esterni" setting_capture_external_links_text: > Se l'opzione è abilitata, tutti i link esterni in testo formattato verranno reindirizzati a una pagina di avviso prima di uscire dall'applicazione. Questo aiuta a proteggere gli utenti da siti web esterni potenzialmente dannosi. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "Reindirizzamento di primo accesso" setting_after_first_login_redirect_url_text_html: > Imposta un percorso per reindirizzare gli utenti dopo il loro primo accesso. Se vuoto, reindirizza alla home page per il tour di onboarding.
    Esempio: /my/page diff --git a/config/locales/crowdin/ja.yml b/config/locales/crowdin/ja.yml index f753b1adcfc..80f3606b362 100644 --- a/config/locales/crowdin/ja.yml +++ b/config/locales/crowdin/ja.yml @@ -113,7 +113,7 @@ ja: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ ja: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -3921,6 +3921,7 @@ ja: notice_successful_delete: "正常に削除しました。" notice_successful_cancel: "キャンセルしました。" notice_successful_update: "正常に更新しました。" + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "作成に失敗しました" notice_unsuccessful_create_with_reason: "作成失敗: %{reason}" notice_unsuccessful_update: "更新に失敗しました。" @@ -4265,6 +4266,9 @@ ja: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "最初のログインリダイレクト" setting_after_first_login_redirect_url_text_html: > 最初のログイン後にユーザーをリダイレクトするパスを設定します。空の場合は、オンボーディングツアーのホームページにリダイレクトします。
    例: /my/page diff --git a/config/locales/crowdin/js-tr.yml b/config/locales/crowdin/js-tr.yml index 78ce77f2b82..d3ffbfb40bf 100644 --- a/config/locales/crowdin/js-tr.yml +++ b/config/locales/crowdin/js-tr.yml @@ -154,7 +154,7 @@ tr: attribute_reference: macro_help_tooltip: "Bu metin parçası bir makro tarafından dinamik olarak işleniyor." not_found: "İstenen kaynak bulunamadı" - nested_macro: "This macro is recursively referencing %{model} %{id}." + nested_macro: "Bu makro özyinelemeli olarak %{model} %{id}adresine referans verir." invalid_attribute: "Seçilen '%{name}' özniteliği mevcut değil." child_pages: button: "Alt sayfalara bağlantılar" @@ -203,8 +203,8 @@ tr: add_table: "Bağlantılı iş paketlerinin tablosunu ekle" edit_query: "Sorguyu düzenle" new_group: "Yeni grup" - delete_group: "Delete group" - remove_attribute: "Remove from group" + delete_group: "Grubu Sil" + remove_attribute: "Gruptan çıkar" reset_to_defaults: "Varsayılanlara geri yükle" working_days: calendar: @@ -222,9 +222,9 @@ tr: Devam etmek istediğinizden emin misiniz? work_packages_settings: warning_progress_calculation_mode_change_from_status_to_field_html: >- - Changing progress calculation mode from status-based to work-based will make the % Complete field freely editable. If you optionally enter values for Work or Remaining work, they will also be linked to % Complete. Changing Remaining work can then update % Complete. + İlerleme hesaplama modunu durum bazlıdan iş bazlıya değiştirmek % Tamamlandı alanını serbestçe düzenlenebilir hale getirecektir. İş veya Kalan iş için isteğe bağlı olarak değerler girerseniz, bunlar da % Tamamlandı ile bağlantılı olacaktır. Kalan işin değiştirilmesi % Tamamlanan'ı güncelleyebilir. warning_progress_calculation_mode_change_from_field_to_status_html: >- - Changing progress calculation mode from work-based to status-based will result in all existing % Complete values to be lost and replaced with values associated with each status. Existing values for Remaining work may also be recalculated to reflect this change. This action is not reversible. + İlerleme hesaplama modunun iş bazlıdan durum bazlıya değiştirilmesi, mevcut tüm % Tamamlandı değerlerinin kaybolmasına ve her bir durumla ilişkili değerlerle değiştirilmesine neden olacaktır. Kalan iş için mevcut değerler de bu değişikliği yansıtacak şekilde yeniden hesaplanabilir. Bu eylem geri döndürülemez. custom_actions: date: specific: "tarihinde" @@ -295,17 +295,17 @@ tr: ical_sharing_modal: title: "Takvime abone ol" inital_setup_error_message: "Veri alınırken bir hata oluştu." - description: "You can use the URL (iCalendar) to subscribe to this calendar in an external client and view up-to-date work package information from there." + description: "Harici bir istemcide bu takvime abone olmak için URL'yi (iCalendar) kullanabilir ve güncel iş paketi bilgilerini buradan görüntüleyebilirsiniz." warning: "Bu adresi başkalarıyla paylaşmayın. Bu adrese sahip olan birisi iş paketini hesabı ya da parolası olmaksızın görüntüleyebilecektir." token_name_label: "Bunu nerede kullanacaksınız?" token_name_placeholder: 'Bir ad yazın, örneğin "Telefon"' - token_name_description_text: 'If you subscribe to this calendar from multiple devices, this name will help you distinguish between them in your access tokens list.' + token_name_description_text: 'Bu takvime birden fazla cihazdan abone olursanız, bu ad erişim belirteçleri listenizde bunlar arasında ayrım yapmanıza yardımcı olacaktır.' copy_url_label: "Adresi kopyala" ical_generation_error_text: "Takvim adresi üretilirken bir hata oldu." success_message: '"%{name}" adresi başarıyla panonuza koplandı. Aboneliği tamamlamak için takvim istemcinize yapıştırın.' label_activate: "Etkinleştir" label_assignee: "Atanan" - label_assignee_alt_text: "This work package is assigned to %{name}" + label_assignee_alt_text: "Bu iş paketi %{name} ne atanmıştır." label_add_column_after: "Sonrasına sütun ekle" label_add_column_before: "Öncesine sütun ekle" label_add_columns: "Sütunlar ekle" @@ -347,7 +347,7 @@ tr: label_collapse: "Daralt" label_collapsed: "daraltılmış" label_collapse_all: "Tümünü daralt" - label_collapse_text: "Collapse text" + label_collapse_text: "Metni daralt" label_comment: "Yorum" label_committed_at: "%{committed_revision_link} %{date}" label_committed_link: "kaydedilmiş revizyon %{revision_identifier}" @@ -360,7 +360,7 @@ tr: label_expand: "Genişlet" label_expanded: "genişletilmiş" label_expand_all: "Tümünü genişlet" - label_expand_text: "Show full text" + label_expand_text: "Tam metni göster" label_expand_project_menu: "Proje menüsünü genişlet" label_export: "Dışarı aktar" label_export_preparing: "İhracat hazırlanıyor ve kısa süre içinde indirilecek." @@ -426,7 +426,7 @@ tr: label_remove_row: "Satırı kaldır" label_report: "Rapor" label_repository_plural: "Depolar" - label_resize_project_menu: "Resize project menu" + label_resize_project_menu: "Proje menüsünü yeniden boyutlandır" label_save_as: "Farklı Kaydet" label_search_columns: "Bir sütunda arama yap" label_select_watcher: "Takipçi seç..." @@ -573,7 +573,7 @@ tr: with_current_filter: "Şu anda bu görünümde bildirim yok" mark_all_read: "Tümünü okundu olarak işaretle" mark_as_read: "Okundu olarak işaretle" - mark_all_read_confirmation: "This will mark all notifications in this view as read. Are you sure you want to do this?" + mark_all_read_confirmation: "Bu, bu görünümdeki tüm bildirimleri okundu olarak işaretleyecektir. Bunu yapmak istediğinizden emin misiniz?" text_update_date_by: "%{date} tarafından" total_count_warning: "En yeni %{newest_count} bildirim gösteriliyor. %{more_count} tane daha görüntülenmiyor." empty_state: @@ -689,7 +689,7 @@ tr: last_day: "Son gün" text_are_you_sure: "Emin misiniz?" text_are_you_sure_to_cancel: "Bu sayfada kaydedilmemiş değişiklikleriniz var. Bunları atmak istediğinizden emin misiniz?" - breadcrumb: "Breadcrumb" + breadcrumb: "Gezinti Menüsü" text_data_lost: "Girilen tüm veriler silinecek." text_user_wrote: "%{value} demiş ki:" types: @@ -851,14 +851,14 @@ tr: progress: title: "İş tahminleri ve ilerleme" baseline: - addition_label: "Added to view within the comparison time period" - removal_label: "Removed from view within the comparison time period" - modification_label: "Modified within the comparison time period" - column_incompatible: "This column does not show changes in Baseline mode." + addition_label: "Karşılaştırma süresi içinde görüntülenmek üzere eklendi" + removal_label: "Karşılaştırma süresi içinde görünümden kaldırıldı" + modification_label: "Karşılaştırma dönemi içinde değiştirildi" + column_incompatible: "Bu sütun Temel moddaki değişiklikleri göstermez." filters: title: "İş paketlerini filtrele" - baseline_incompatible: "This filter attribute is not taken into consideration in Baseline mode." - baseline_warning: "Baseline mode is on but some of your active filters are not included in the comparison." + baseline_incompatible: "Bu filtre özelliği Temel modda dikkate alınmaz." + baseline_warning: "Baseline modu açık ancak aktif filtrelerinizden bazıları karşılaştırmaya dahil edilmiyor." inline_create: title: "Listeden yeni bir iş paketi eklemek için buraya tıklayınız" create: @@ -990,7 +990,7 @@ tr: button: "Bu iş paketi tablosunu yapılandır" choose_display_mode: "İş paketlerini farklı olarak göster" modal_title: "İş paketi tablosu yapılandırma" - embedded_tab_disabled: "This configuration tab is not available for the embedded view you are editing." + embedded_tab_disabled: "Bu yapılandırma sekmesi, düzenlemekte olduğunuz gömülü görünüm için kullanılamaz." default: "varsayılan" display_settings: "Görünüm ayarları" default_mode: "Düz liste" @@ -1072,7 +1072,7 @@ tr: notice_successful_delete: "Silme başarılı." notice_successful_update: "Güncelleme başarılı." notice_job_started: "görev başlatıldı." - no_job_id: "No job ID returned from server." + no_job_id: "Sunucudan dönen iş kimliği yok." invalid_job_response: "Sunucudan geçersiz yanıt." notice_bad_request: "Hatalı istek." relations: @@ -1124,12 +1124,12 @@ tr: global_search: all_projects: "Tüm projelerde" close_search: "Aramayı kapat" - items_available: "%{count} items available" - direct_hit_available: "Work package with exact ID found. Press Enter to open it." + items_available: "%{count} mevcut ürün" + direct_hit_available: "Tam kimliğe sahip iş paketi bulundu. Açmak için Enter tuşuna basın." current_project_and_all_descendants: "Bu projede + alt projelerde" current_project: "Bu projede" recently_viewed: "Son görüntülenenler" - search_placeholder_expanded: "Search work packages by subject, project, type, status or ID" + search_placeholder_expanded: "İş paketlerini konu, proje, tür, durum veya kimliğe göre arama" title: all_projects: "tüm projeler" project_and_subprojects: "ve tüm alt projeler" @@ -1140,12 +1140,12 @@ tr: timeline: "Gantt" invite_user_modal: invite: "Davet et" - placeholder_add_tag: "Create placeholder user" + placeholder_add_tag: "Geçici (placeholder) kullanıcı oluştur" exclusion_info: modal: - title: "Status excluded from hierarchy totals" + title: "Hiyerarşi toplamlarından hariç tutulan durum" content: >- - The status '%{status_name}' has been configured to be excluded from hierarchy totals of Work, Remaining work, and % Complete. The totals do not take this value into account. + '%{status_name}' durumu, İş, Kalan iş ve % Tamamlandı hiyerarşi toplamlarından hariç tutulacak şekilde yapılandırılmıştır. Toplamlar bu değeri dikkate almaz. favorite_projects: no_results: "Favori projeniz yok" no_results_subtext: "Bir veya birden fazla projeyi genel bakışları veya proje listesi aracılığıyla favori olarak ekleyin." @@ -1167,10 +1167,10 @@ tr: no_results: "Arama kriterleriniz ile eşleşen proje yok" no_favorite_results: "Arama kriterleriniz ile eşleşen favori proje yok." include_workspaces: - search_placeholder: "Search..." + search_placeholder: "Arama..." types: program: "Program" - portfolio: "Portfolio" + portfolio: "Portföy" baseline: toggle_title: "Referens Zamanı" clear: "Temizle" @@ -1198,8 +1198,8 @@ tr: maintained_with_changes: "Değişikliklerle sürdürüldü" in_your_timezone: "Yerel saat diliminizde:" icon_tooltip: - added: "Added to view within the comparison time period" - removed: "Removed from view within the comparison time period" + added: "Karşılaştırma süresi içinde görüntülenmek üzere eklendi" + removed: "Karşılaştırma süresi içinde görünümden kaldırıldı" changed: "Değişiklikler ile sürdürüldü" forms: submit_success_message: "Form başarıyla gönderildi" diff --git a/config/locales/crowdin/ka.yml b/config/locales/crowdin/ka.yml index 70edb183201..fb4def25f90 100644 --- a/config/locales/crowdin/ka.yml +++ b/config/locales/crowdin/ka.yml @@ -113,7 +113,7 @@ ka: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ ka: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -3972,6 +3972,7 @@ ka: notice_successful_delete: "Successful deletion." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Successful update." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4317,6 +4318,9 @@ ka: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/kk.yml b/config/locales/crowdin/kk.yml index 6418301e87c..9618b73791a 100644 --- a/config/locales/crowdin/kk.yml +++ b/config/locales/crowdin/kk.yml @@ -113,7 +113,7 @@ kk: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ kk: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -3972,6 +3972,7 @@ kk: notice_successful_delete: "Successful deletion." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Successful update." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4317,6 +4318,9 @@ kk: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/ko.yml b/config/locales/crowdin/ko.yml index 9200f03ded8..ba653dce819 100644 --- a/config/locales/crowdin/ko.yml +++ b/config/locales/crowdin/ko.yml @@ -113,7 +113,7 @@ ko: index: description: "모델 컨텍스트 프로토콜을 통해 AI 에이전트는 이 OpenProject 인스턴스에 의해 노출된 도구와 리소스를 사용자에게 제공할 수 있습니다." resources_heading: "리소스" - resources_description: "OpenProject는 다음 도구를 구현합니다. 원하는 대로 각 도구를 활성화하고, 이름을 바꾸고, 설명할 수 있습니다. 자세한 내용은 [MCP 리소스의 문서](docs_url)를 참조하세요." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "리소스 업데이트" tools_heading: "도구" tools_description: "OpenProject는 다음 도구를 구현합니다. 원하는 대로 각 도구를 활성화하고, 이름을 바꾸고, 설명할 수 있습니다. 자세한 내용은 [MCP 도구의 문서](docs_url)를 참조하세요." @@ -122,7 +122,7 @@ ko: success: "MCP 구성이 업데이트되었습니다." server_form: description_caption: "MCP 서버에 연결하는 다른 애플리케이션에 대해 MCP 서버가 설명되는 방식입니다." - title_caption: "MCP 서버에 연결하는 애플리케이션에 표시되는 짧은 제목입니다." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP 구성을 업데이트할 수 없습니다." success: "MCP 구성이 업데이트되었습니다." @@ -3922,6 +3922,7 @@ ko: notice_successful_delete: "삭제에 성공했습니다." notice_successful_cancel: "취소에 성공했습니다." notice_successful_update: "업데이트에 성공했습니다." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "생성이 실패했습니다." notice_unsuccessful_create_with_reason: "생성 실패: %{reason}" notice_unsuccessful_update: "업데이트에 실패했습니다." @@ -4263,6 +4264,9 @@ ko: setting_capture_external_links: "외부 링크 캡처" setting_capture_external_links_text: > 활성화된 경우, 서식이 지정된 텍스트의 모든 외부 링크는 애플리케이션을 종료하기 전에 경고 페이지를 통해 리디렉션됩니다. 따라서 잠재적인 악성 외부 웹사이트로부터 사용자를 보호할 수 있습니다. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "첫 번째 로그인 리디렉션" setting_after_first_login_redirect_url_text_html: > 첫 로그인 후 사용자를 리디렉션할 경로를 설정하세요. 비어 있으면 온보딩 투어의 홈페이지로 리디렉션됩니다.
    예: /my/page diff --git a/config/locales/crowdin/lt.yml b/config/locales/crowdin/lt.yml index db55c71f94f..e221543b00b 100644 --- a/config/locales/crowdin/lt.yml +++ b/config/locales/crowdin/lt.yml @@ -113,7 +113,7 @@ lt: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ lt: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -4069,6 +4069,7 @@ lt: notice_successful_delete: "Sėkmingas panaikinimas." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Sėkmingai atnaujinta." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4413,6 +4414,9 @@ lt: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/lv.yml b/config/locales/crowdin/lv.yml index e26ba35ef69..beb9ee014c7 100644 --- a/config/locales/crowdin/lv.yml +++ b/config/locales/crowdin/lv.yml @@ -113,7 +113,7 @@ lv: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ lv: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -4022,6 +4022,7 @@ lv: notice_successful_delete: "Successful deletion." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Successful update." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4368,6 +4369,9 @@ lv: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/mn.yml b/config/locales/crowdin/mn.yml index c8a8ec01ae1..198b856ffb0 100644 --- a/config/locales/crowdin/mn.yml +++ b/config/locales/crowdin/mn.yml @@ -113,7 +113,7 @@ mn: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ mn: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -3972,6 +3972,7 @@ mn: notice_successful_delete: "Successful deletion." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Successful update." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4317,6 +4318,9 @@ mn: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/ms.yml b/config/locales/crowdin/ms.yml index fce97146ed6..dbcc526784d 100644 --- a/config/locales/crowdin/ms.yml +++ b/config/locales/crowdin/ms.yml @@ -113,7 +113,7 @@ ms: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ ms: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -3920,6 +3920,7 @@ ms: notice_successful_delete: "Pemadaman yang berjaya." notice_successful_cancel: "Pembatalan yang berjaya." notice_successful_update: "Kemas kini berjaya." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4263,6 +4264,9 @@ ms: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/ne.yml b/config/locales/crowdin/ne.yml index b8963889ff5..4044a3b43d5 100644 --- a/config/locales/crowdin/ne.yml +++ b/config/locales/crowdin/ne.yml @@ -113,7 +113,7 @@ ne: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ ne: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -3972,6 +3972,7 @@ ne: notice_successful_delete: "Successful deletion." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Successful update." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4317,6 +4318,9 @@ ne: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/nl.yml b/config/locales/crowdin/nl.yml index 26b41776542..ebf15731969 100644 --- a/config/locales/crowdin/nl.yml +++ b/config/locales/crowdin/nl.yml @@ -113,7 +113,7 @@ nl: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ nl: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -3967,6 +3967,7 @@ nl: notice_successful_delete: "Verwijdering geslaagd." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Succesvolle wijziging." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4312,6 +4313,9 @@ nl: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/no.yml b/config/locales/crowdin/no.yml index 67764a59f7e..079c55421ce 100644 --- a/config/locales/crowdin/no.yml +++ b/config/locales/crowdin/no.yml @@ -113,7 +113,7 @@ index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -3971,6 +3971,7 @@ notice_successful_delete: "Slettingen var vellykket." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Oppdateringen var vellykket." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4316,6 +4317,9 @@ setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/pl.yml b/config/locales/crowdin/pl.yml index 232cc027fba..1ec15758fb8 100644 --- a/config/locales/crowdin/pl.yml +++ b/config/locales/crowdin/pl.yml @@ -113,7 +113,7 @@ pl: index: description: "Protokół kontekstu modelu umożliwia agentom AI dostarczanie użytkownikom narzędzi i zasobów udostępnianych przez to wystąpienie OpenProject." resources_heading: "Zasoby" - resources_description: "OpenProject implementuje następujące narzędzia. Każde z nich może zostać włączone, przemianowane i opisane zgodnie z Twoim życzeniem. Aby uzyskać więcej informacji, zapoznaj się z [dokumentacją zasobów MCP](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Zaktualizuj zasoby" tools_heading: "Narzędzia" tools_description: "OpenProject implementuje następujące narzędzia. Każde z nich może zostać włączone, przemianowane i opisane zgodnie z Twoim życzeniem. Aby uzyskać więcej informacji, zapoznaj się z [dokumentacją narzędzi MCP](docs_url)." @@ -122,7 +122,7 @@ pl: success: "Konfiguracje MCP zostały zaktualizowane." server_form: description_caption: "Sposób, w jaki serwer MCP będzie opisywany innym aplikacjom, które się z nim łączą." - title_caption: "Krótki tytuł wyświetlany aplikacjom łączącym się z serwerem MCP." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "Nie można zaktualizować konfiguracji MCP." success: "Konfiguracja MCP została zaktualizowana." @@ -4067,6 +4067,7 @@ pl: notice_successful_delete: "Usuwanie zakończone sukcesem." notice_successful_cancel: "Anulowanie powiodło się." notice_successful_update: "Aktualizacja zakończona sukcesem." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Utworzenie nie powiodło się." notice_unsuccessful_create_with_reason: "Utworzenie nie powiodło się: %{reason}" notice_unsuccessful_update: "Aktualizacja nie powiodła się." @@ -4412,6 +4413,9 @@ pl: setting_capture_external_links: "Przechwyć linki zewnętrzne" setting_capture_external_links_text: > Po włączeniu tej funkcji wszystkie linki zewnętrzne w tekście formatowanym będą przekierowywać na stronę z ostrzeżeniem przed opuszczeniem aplikacji. Pomaga to chronić użytkowników przed potencjalnie złośliwymi zewnętrznymi witrynami internetowymi. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "Przekierowanie pierwszego logowania" setting_after_first_login_redirect_url_text_html: > Ustaw ścieżkę przekierowania użytkowników po ich pierwszym zalogowaniu. Jeśli jest pusta, przekierowuje do strony głównej wycieczki wdrożeniowej.
    Przykład: /my/page diff --git a/config/locales/crowdin/pt-BR.yml b/config/locales/crowdin/pt-BR.yml index 7058904f55c..8c81dcabe28 100644 --- a/config/locales/crowdin/pt-BR.yml +++ b/config/locales/crowdin/pt-BR.yml @@ -113,7 +113,7 @@ pt-BR: index: description: "O protocolo de contexto do modelo permite que agentes de IA forneçam aos seus usuários ferramentas e recursos disponibilizados por esta instância do OpenProject." resources_heading: "Recursos" - resources_description: "O OpenProject implementa as seguintes ferramentas. Cada uma pode ser habilitada, renomeada e descrita conforme desejado. Para mais informações, consulte a [documentação sobre recursos MCP](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Atualizar recursos" tools_heading: "Ferramentas" tools_description: "O OpenProject implementa as seguintes ferramentas. Cada uma pode ser habilitada, renomeada e descrita conforme desejado. Para mais informações, consulte a [documentação sobre ferramentas MCP](docs_url)." @@ -122,7 +122,7 @@ pt-BR: success: "Configurações MCP atualizadas com sucesso." server_form: description_caption: "Como o servidor MCP será descrito para outros aplicativos que se conectarem a ele." - title_caption: "Um título curto exibido para aplicativos que se conectam ao servidor MCP." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "A configuração MCP não pôde ser atualizada." success: "Configuração MCP atualizada com sucesso." @@ -3968,6 +3968,7 @@ pt-BR: notice_successful_delete: "Exclusão bem sucedida." notice_successful_cancel: "Cancelamento bem-sucedido." notice_successful_update: "Atualizado com sucesso." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Falha na criação." notice_unsuccessful_create_with_reason: "Falha na criação: %{reason}" notice_unsuccessful_update: "Falha na atualização." @@ -4313,6 +4314,9 @@ pt-BR: setting_capture_external_links: "Capturar links externos" setting_capture_external_links_text: > Quando ativado, todos os links externos em textos formatados serão redirecionados por uma página de aviso antes de sair do aplicativo. Isso ajuda a proteger os usuários de sites externos potencialmente maliciosos. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "Redirecionamento de primeiro login" setting_after_first_login_redirect_url_text_html: > Defina um caminho para redirecionar os usuários após o primeiro login. Se deixado em branco, eles serão redirecionados para a página inicial do tour de introdução.
    Exemplo: /my/page diff --git a/config/locales/crowdin/pt-PT.yml b/config/locales/crowdin/pt-PT.yml index 3c5adf5e4ca..6dccc8089d5 100644 --- a/config/locales/crowdin/pt-PT.yml +++ b/config/locales/crowdin/pt-PT.yml @@ -113,7 +113,7 @@ pt-PT: index: description: "O protocolo de contexto do modelo permite que os agentes de IA forneçam aos seus utilizadores ferramentas e recursos expostos por esta instância do OpenProject." resources_heading: "Recursos" - resources_description: "O OpenProject implementa as seguintes ferramentas. Cada uma pode ser ativada, renomeada e descrita como quiser. Para mais informações, consulte a [documentação sobre recursos MCP](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Atualizar recursos" tools_heading: "Ferramentas" tools_description: "O OpenProject implementa as seguintes ferramentas. Cada uma pode ser ativada, renomeada e descrita como quiser. Para mais informações, consulte a [documentação sobre ferramentas MCP](docs_url)." @@ -122,7 +122,7 @@ pt-PT: success: "As configurações da MCP foram atualizadas com êxito." server_form: description_caption: "Como o servidor MCP será descrito a outras aplicações que se ligam a ele." - title_caption: "Um título curto apresentado às aplicações que se ligam ao servidor MCP." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "Não foi possível atualizar a configuração MCP." success: "A configuração da MCP foi atualizada com êxito." @@ -3968,6 +3968,7 @@ pt-PT: notice_successful_delete: "Eliminado com sucesso." notice_successful_cancel: "Cancelamento bem-sucedido." notice_successful_update: "Actualizado com sucesso." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "A criação falhou." notice_unsuccessful_create_with_reason: "A criação falhou: %{reason}" notice_unsuccessful_update: "Falha na atualização." @@ -4311,6 +4312,9 @@ pt-PT: setting_capture_external_links: "Capturar links externos" setting_capture_external_links_text: > Quando ativada, todos os links externos no texto formatado serão redirecionados através de uma página de aviso antes de sair da aplicação. Isto ajuda a proteger os utilizadores de sites externos potencialmente maliciosos. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "Redirecionamento do primeiro início de sessão" setting_after_first_login_redirect_url_text_html: > Defina um caminho para redirecionar os utilizadores após o primeiro início de sessão. Se estiver vazio, redireciona para a página inicial do tour de integração.
    Exemplo: /my/page diff --git a/config/locales/crowdin/ro.yml b/config/locales/crowdin/ro.yml index 4f621f6329b..ab4d512fd63 100644 --- a/config/locales/crowdin/ro.yml +++ b/config/locales/crowdin/ro.yml @@ -113,7 +113,7 @@ ro: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ ro: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -4021,6 +4021,7 @@ ro: notice_successful_delete: "Ştergere reuşită." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Actualizare reușită." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4367,6 +4368,9 @@ ro: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/ru.yml b/config/locales/crowdin/ru.yml index 1a680921a8f..e81c2224c2d 100644 --- a/config/locales/crowdin/ru.yml +++ b/config/locales/crowdin/ru.yml @@ -113,7 +113,7 @@ ru: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ ru: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -1533,7 +1533,7 @@ ru: even: "должно быть чётным." exclusion: "зарезервировано." feature_disabled: недоступно. - feature_disabled_for_project: is disabled for this project. + feature_disabled_for_project: отключен для этого проекта. file_too_large: "слишком большой (максимальный размер составляет %{count} байт)." filter_does_not_exist: "фильтр не существует." format: "не соответствует ожидаемому формату '%{expected}'." @@ -2550,7 +2550,7 @@ ru: baseline_comparison: Сравнение базовых уровней board_view: Расширенные доски calculated_values: Рассчитываемые значения - capture_external_links: Capture External Links + capture_external_links: Перехват внешних ссылок internal_comments: Служебный комментарий custom_actions: Пользовательские действия custom_field_hierarchies: Иерархии @@ -2607,7 +2607,7 @@ ru: customize_life_cycle: description: "Создание и организация этапов и стадий проекта, отличных от тех, что предусмотрены планированием цикла проекта PM2." capture_external_links: - description: "Prevent social engineering attacks by capturing and warning about external links before users visit them." + description: "Предотвращать атаки социальной инженерии путем перехвата внешних ссылок до того, как пользователи их посетят." work_package_query_relation_columns: description: "Нужно видеть отношения или дочерние элементы в списке пакетов работ?" edit_attribute_groups: @@ -2953,15 +2953,15 @@ ru: #We need to include the version to invalidate outdated translations in other locales "17_1": new_features_title: > - The release contains various new features and improvements, such as: + Релиз содержит различные новые функции и улучшения, такие как: new_features_list: line_0: Automated project initiation (Enterprise add-on). - line_1: "Meetings: add new or existing work packages as outcomes." + line_1: "Совещания: добавление новых или существующих пакетов работ в качестве итогов совещания." line_2: "Meetings: show iCal responses in OpenProject." - line_3: "Recurring meetings: duplicate agenda items to the next occurrence." - line_4: "Release to Community: Attribute highlighting." - line_5: Warning before opening external links in user-provided content (Enterprise add-on). - line_6: Improved performance and user experience, including the Activity tab and Documents module. + line_3: "Повторяющиеся совещания: продублируйте пункты повестки дня на следующее совещание." + line_4: "Выпуск для сообщества: Выделение атрибутов." + line_5: Предупреждение перед открытием внешних ссылок в содержании, предоставленном пользователем (Enterprise add-on). + line_6: Улучшена производительность и пользовательский опыт, включая вкладку "Деятельность" и модуль "Документы". links: upgrade_enterprise_edition: "Обновить до корпоративной версии" postgres_migration: "Перенос вашей установки в PostgreSQL" @@ -3062,7 +3062,7 @@ ru: text_hint: "Токены API позволяют приложениям общаться с этим экземпляром OpenProject через REST API." static_token_name: "Токен API" disabled_text: "Токены API не включены администратором. Пожалуйста, обратитесь к администратору для использования этой функции." - add_button: "API token" + add_button: "API-токен" ical: blank_description: "Чтобы добавить токен iCalendar, подпишитесь на новый или существующий календарь из модуля календаря проекта. Вы должны иметь необходимые разрешения." blank_title: "Нет токена iCalendar" @@ -3090,7 +3090,7 @@ ru: removed: "Токен OAuth успешно удален" unknown_integration: "Неизвестная" token/rss: - add_button: "RSS token" + add_button: "Токен RSS" blank_description: "Токен RSS пока не создан. Вы можете создать его с помощью кнопки ниже." blank_title: "Нет токена RSS" title: "RSS" @@ -4069,6 +4069,7 @@ ru: notice_successful_delete: "Удаление выполнено." notice_successful_cancel: "Отмена прошла успешно." notice_successful_update: "Обновление выполнено." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Создание не удалось." notice_unsuccessful_create_with_reason: "Создание не удалось: %{reason}" notice_unsuccessful_update: "Ошибка обновления." @@ -4411,9 +4412,12 @@ ru: setting_allowed_link_protocols: "Разрешить ссылки на протоколы" setting_allowed_link_protocols_text_html: >- Разрешить отображать эти протоколы как ссылки в описаниях пакетов работ, длинных текстовых полях и комментариях. Например, %{tel_code} или %{element_code}. Введите по одному протоколу в строке.
    Протоколы %{http_code}, %{https_code} и %{mailto_code} всегда разрешены. - setting_capture_external_links: "Capture external links" + setting_capture_external_links: "Перехват внешних ссылок" setting_capture_external_links_text: > - When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + Если эта функция включена, все внешние ссылки в форматированном тексте будут перенаправляться через страницу предупреждения, прежде чем покинуть приложение. Это помогает защитить пользователей от потенциально вредоносных внешних сайтов. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "Перенаправление первого входа" setting_after_first_login_redirect_url_text_html: > Задайте путь для перенаправления пользователей после их первого входа в систему. Если не задано, то пользователь будет перенаправлен на домашнюю страницу обзорного тура.
    Например: /my/page @@ -5396,7 +5400,7 @@ ru: hashed_token: display_value_placeholder: "***" external_link_warning: - title: "Leaving OpenProject" - warning_message: "You are about to leave OpenProject and visit an external website. Please be aware that external websites are not under our control and may have different privacy and security policies." - continue_message: "Are you sure you want to proceed to the following external link?" + title: "Выход из OpenProject" + warning_message: "Вы собираетесь покинуть OpenProject и посетить внешний сайт. Пожалуйста, имейте в виду, что внешние сайты не находятся под нашим контролем и могут иметь другую политику конфиденциальности и безопасности." + continue_message: "Вы уверены, что хотите перейти по следующей внешней ссылке?" continue_button: "Перейти на внешний сайт" diff --git a/config/locales/crowdin/rw.yml b/config/locales/crowdin/rw.yml index 736e236361f..44f8134f3aa 100644 --- a/config/locales/crowdin/rw.yml +++ b/config/locales/crowdin/rw.yml @@ -113,7 +113,7 @@ rw: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ rw: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -3972,6 +3972,7 @@ rw: notice_successful_delete: "Successful deletion." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Successful update." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4317,6 +4318,9 @@ rw: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/si.yml b/config/locales/crowdin/si.yml index 49c437a24dd..e96ba91d87a 100644 --- a/config/locales/crowdin/si.yml +++ b/config/locales/crowdin/si.yml @@ -113,7 +113,7 @@ si: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ si: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -3972,6 +3972,7 @@ si: notice_successful_delete: "සාර්ථක මකාදැමීම." notice_successful_cancel: "Successful cancellation." notice_successful_update: "සාර්ථක යාවත්කාලීන කිරීම." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4317,6 +4318,9 @@ si: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/sk.yml b/config/locales/crowdin/sk.yml index f40417abbab..a5bfa01c932 100644 --- a/config/locales/crowdin/sk.yml +++ b/config/locales/crowdin/sk.yml @@ -113,7 +113,7 @@ sk: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ sk: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -4071,6 +4071,7 @@ sk: notice_successful_delete: "Úspešne zmazané." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Úspešne aktualizované." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4418,6 +4419,9 @@ sk: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/sl.yml b/config/locales/crowdin/sl.yml index 4054bb2568d..af19b7b697b 100644 --- a/config/locales/crowdin/sl.yml +++ b/config/locales/crowdin/sl.yml @@ -113,7 +113,7 @@ sl: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ sl: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -4071,6 +4071,7 @@ sl: notice_successful_delete: "Uspešen izbris." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Posodobitev uspela." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4417,6 +4418,9 @@ sl: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/sr.yml b/config/locales/crowdin/sr.yml index c52519072d5..a2372bfc75b 100644 --- a/config/locales/crowdin/sr.yml +++ b/config/locales/crowdin/sr.yml @@ -113,7 +113,7 @@ sr: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ sr: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -4022,6 +4022,7 @@ sr: notice_successful_delete: "Successful deletion." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Successful update." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4368,6 +4369,9 @@ sr: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/sv.yml b/config/locales/crowdin/sv.yml index 4f3dc5245d8..d95490614a8 100644 --- a/config/locales/crowdin/sv.yml +++ b/config/locales/crowdin/sv.yml @@ -113,7 +113,7 @@ sv: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ sv: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -3972,6 +3972,7 @@ sv: notice_successful_delete: "Raderades utan problem." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Uppdaterades utan problem." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Uppdateringen misslyckades." @@ -4315,6 +4316,9 @@ sv: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/th.yml b/config/locales/crowdin/th.yml index 62f8553d8cc..3535c0a47f4 100644 --- a/config/locales/crowdin/th.yml +++ b/config/locales/crowdin/th.yml @@ -113,7 +113,7 @@ th: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ th: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -3922,6 +3922,7 @@ th: notice_successful_delete: "ลบเรียบร้อยแล้ว" notice_successful_cancel: "Successful cancellation." notice_successful_update: "ปรับปรุงข้อมูลเรียบร้อยแล้ว" + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4266,6 +4267,9 @@ th: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/tr.yml b/config/locales/crowdin/tr.yml index c284f0f3a87..cbed0c5f959 100644 --- a/config/locales/crowdin/tr.yml +++ b/config/locales/crowdin/tr.yml @@ -111,21 +111,21 @@ tr: link: "webhook" mcp_configurations: index: - description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." - resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." - resources_submit: "Update resources" - tools_heading: "Tools" - tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." - tools_submit: "Update tools" + description: "MCP (Model Bağlam Protokolü), AI ajanlarının bu OpenProject sisteminde sunulan araç ve kaynakları kullanıcılara erişilebilir kılmasını sağlar." + resources_heading: "Kaynaklar" + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_submit: "Kaynakları güncelle" + tools_heading: "Araçlar" + tools_description: "OpenProject aşağıdaki araçları destekler. Her biri etkinleştirilebilir, adı ve açıklaması değiştirilebilir. Daha fazla bilgi için [documentation on MCP tools](docs_url)." + tools_submit: "Araçları güncelle" multi_update: - success: "MCP configurations were updated successfully." + success: "MCP yapılandırmaları başarıyla güncellendi." server_form: - description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + description_caption: "MCP sunucusunun, ona bağlanan diğer uygulamalara nasıl tanımlanacağı." + title_caption: "A short title shown to applications that connect to the MCP server." update: - failure: "MCP configuration could not be updated." - success: "MCP configuration was updated successfully." + failure: "MCP yapılandırması güncellenemedi." + success: "MCP yapılandırması başarıyla güncellendi." scim_clients: authentication_methods: sso: "Kimlik sağlayıcıdan JWT" @@ -208,7 +208,7 @@ tr: error_cannot_act_self: "Kendi yüklediğiniz dosyalar üzerinde işlem yapamazsınız." attribute_help_texts: caption: "This short version will be displayed as caption of the attribute." - note_public: "Any text and images you add to this field are publicly visible to all logged in users." + note_public: "Bu alana eklediğiniz herhangi bir metin ve resim, oturum açmış tüm kullanıcılar tarafından görülebilir!" text_overview: "Bu görünümde, öznitelikleri görünüm için özel yardım metinler oluşturabilirsiniz. Tanımladığınızda, bu metinler, aidiyet öznitelik yanındaki Yardım simgesini tıklayarak gösterilebilir." show_preview: "Metni önizle" add_new: "Yardım metni ekle" @@ -369,7 +369,7 @@ tr: instructions: is_required: all: "Mark the custom field as required. This will make it mandatory to fill in the field when creating new resources. Existing resources will not require a value when being updated." - project: "Required attributes need to be filled out by the user on project creation if the field is active ('For all projects' set or copying from a project/template in which the field is active). Existing projects will not require a value when being updated." + project: "Alan etkin olduğu durumda, zorunlu alanlar yalnızca proje oluşturulurken doldurulmak zorundadır(‘Tüm projeler için’ ayarı yapılmışsa veya alanın etkin olduğu bir proje/şablondan kopyalama yapılıyorsa). Mevcut projeler güncellenirken bu alanlar için değer girilmesi gerekmez." is_for_all: all: "Mark the custom field as available in all existing and new projects." project: "Mark the attribute as available in all existing and new projects." @@ -403,8 +403,8 @@ tr: concatenation: single: "ya da" danger_dialog: - confirmation_live_message_checked: "The button to proceed is now active." - confirmation_live_message_unchecked: "The button to proceed is now inactive. You need to tick the checkbox to continue." + confirmation_live_message_checked: "Devam etmek için düğme artık aktif." + confirmation_live_message_unchecked: "Devam etmek için düğme artık aktif değil. Devam etmek için işratlemeniz gerekli." op_dry_validation: or: "ya da" errors: @@ -539,7 +539,7 @@ tr: text: "Bu eylem, listenin içerdiği hiçbir projeyi silmeyecektir. Bu proje listesini silmek istediğinizden emin misiniz?" settings: header_details: Temel bilgiler - header_status: Status + header_status: Durum header_relations: Proje ilişkileri button_update_details: Detayları güncelle button_update_status_description: Durum açıklamasını güncelle @@ -566,11 +566,11 @@ tr: program_template_caption: "Select a template program to be used as the default for new subitems of this type." no_template: "No predefined template" template: - menu_title: "Template" - title: "Template settings" - enable_failed: "Failed to enable template mode." + menu_title: "Şablon" + title: "Şablon ayarları" + enable_failed: "Şablon modu etkinleştirilemedi." members: - excluded_roles_label: "Roles to exclude when template is applied" + excluded_roles_label: "Şablon uygulandığında hariç tutulacak roller" excluded_roles_caption: > Bu şablondan yeni bir proje oluştururken, yukarıda seçilen roller atlanacaktır. Bu, proje rollerine göre hangi üyelerin hariç tutulacağını seçmenize olanak tanır. Kullanıcılar daha sonra şablona görüntüleme amacıyla erişebilir, ancak şablondan oluşturulan yeni projelere erişim izni alamazlar. actions: @@ -609,7 +609,7 @@ tr: is_for_all_blank_slate: heading: For all projects description: This project attribute is enabled in all projects since the "For all projects" option is checked. It cannot be deactivated for individual projects. - enabled_via_assignee_when_submitted_html: This project attribute cannot be disabled since it is set as assignee when submitted for project initiation requests. + enabled_via_assignee_when_submitted_html: Proje başlatma talepleri için gönderildiğinde atanmış olarak ayarlandığından bu proje niteliği devre dışı bırakılamaz. types: no_results_title_text: Şu anda hiçbir tip mevcut değil. form: @@ -637,9 +637,9 @@ tr: label_request_submission: "Request submission" project_attributes_description: > Select which project attributes should be included in the project initiation request. This list only includes [project attributes](project_attributes_url) enabled for for this project. - enabled_because_required_html: This project attribute cannot be disabled for this project initiation request since it is defined as required. This can be changed in the administration settings by the administrator of the instance. + enabled_because_required_html: İlgili proje özniteliği, zorunlu olarak tanımlanmış olması nedeniyle bu proje oluşturma isteğinde pasifleştirilemez. Söz konusu ayar, sistem yöneticisi tarafından yönetim ayarları bölümünden değiştirilebilir. status: - button_edit: Edit status + button_edit: Durumu düzenle wizard: sidebar_content_title: "Content" sections: "Sections" @@ -782,7 +782,7 @@ tr: title: "Oturum yönetimi" instructions: "You are logged in to your account through the following devices. Revoke sessions that you do not recognise or from devices you do not control." may_not_delete_current: "Şuanki oturumunuzu silemezsiniz." - deletion_warning: "Are you sure you want to revoke this session? You will be logged out on this device." + deletion_warning: "Bu oturumu iptal etmek istediğinizden emin misiniz? Bu cihazda oturumunuz kapatılacaktır." groups: member_in_these_groups: "Bu kullanıcı şu anda aşağıdaki grupların bir üyesidir:" no_results_title_text: Bu kullanıcı şu anda hiçbir gruba üye değil. @@ -836,7 +836,7 @@ tr: required: "Lütfen bir rol seçin." message: label: "Davet Mesajı" - description: "We will send an email to the user, to which you can add a personal message here. An explanation for the invitation could be useful, or perhaps a bit of information regarding the project to help them get started." + description: "Kullanıcıya, buradan kişisel bir mesaj ekleyebileceğiniz bir e-posta göndereceğiz. Davet için bir açıklama yararlı olabilir veya belki de başlamalarına yardımcı olacak projeyle ilgili bir parça bilgi olabilir." summary: next_button: "Davet gönder" success_message: @@ -862,7 +862,7 @@ tr: Tablodaki iş paketlerini vurgulamak için kullanılabilir. admin: default: - caption: Making this priority default will override the previous default priority. + caption: Bu önceliği varsayılan yapmak önceki varsayılan önceliği geçersiz kılacaktır. reactions: action_title: "Tepki" add_reaction: "Tepki ekle" @@ -1256,9 +1256,9 @@ tr: port: "Port" tls_certificate_string: "LDAP sunucusu SSL sertifikası" mcp_configuration: - enabled: Enabled - title: Title - description: Description + enabled: Etkin + title: Başlık + description: Açıklama member: roles: "Yetkiler" notification: @@ -1282,8 +1282,8 @@ tr: true: "genel" false: "özel" queries: "Sorgular" - status_code: "Status" - status_explanation: "Status description" + status_code: "Durum" + status_explanation: "Durum açıklaması" status_codes: not_started: "Başlatılmadı" on_track: "Takipte" @@ -1574,7 +1574,7 @@ tr: attributes: content_type: blank: "Dosyanın içerik türü boş olamaz." - not_allowlisted: "The file was rejected by an automatic filter. '%{value}' is not allowed for upload." + not_allowlisted: "Dosya otomatik bir filtre tarafından reddedildi. '%{value}' dosyasının yüklenmesine izin verilmiyor." format: "%{message}" capability: context: @@ -1584,7 +1584,7 @@ tr: minimum: "'=' işleci ile ilke, bağlam veya kimlik için en az bir filtre içermesi gerekir." custom_field: at_least_one_custom_option: "En az bir seçeneğin uygun olması gerekir." - previous_custom_field_recalculation_unprocessed: "The recalculation of previous changes for this custom field have not been applied yet, please try again in a few minutes." + previous_custom_field_recalculation_unprocessed: "Bu özel alana ait önceki değişiklikler henüz yeniden hesaplanmadı. Lütfen birkaç dakika sonra tekrar deneyin." referenced_in_other_fields_html: one: "%{name} is used in project attribute calculation %{links}." other: "%{name} is used in project attribute calculations: %{links}." @@ -1819,7 +1819,7 @@ tr: cannot_be_in_another_project: "başka bir projede olamaz." not_a_valid_parent: "geçersizdir." schedule_manually: - cannot_be_automatically_scheduled: "cannot be set to false (automatically scheduled) as it has no predecessors or children." + cannot_be_automatically_scheduled: "öncülü veya alt öğesi olmadığı için false (otomatik zamanlama) olarak ayarlanamaz." start_date: violates_relationships: "yalnızca %{soonest_start} veya daha sonra iş paketi üst bağımlılık ilişkilerini ihlal etmediğinde ayarlanabilir." cannot_be_null: "başlangıç tarihi ve süresi bilindiğinden sıfır değerine ayarlanamaz." @@ -1904,11 +1904,11 @@ tr: models: attachment: "Dosya" attribute_help_text: - one: "Attribute help text" - other: "Attribute help texts" + one: "Öznitelik yardım metni" + other: "Öznitelik yardım metnileri" auth_provider: - one: "Authentication provider" - other: "Authentication providers" + one: "Kimlik doğrulama sağlayıcısı" + other: "Kimlik doğrulama sağlayıcıları" category: "Kategori" color: "Renk" comment: "Yorum" @@ -1916,15 +1916,15 @@ tr: custom_field: "Özel alan" "doorkeeper/application": "OAuth uygulaması" enterprise_token: - one: "Enterprise token" - other: "Enterprise tokens" + one: "Kurumsal anahtar" + other: "Kurumsal anahtarlar" forum: "Forum" global_role: "Global rol" group: "Grup" issue_priority: one: "Öncelik" other: "Öncelik" - meeting_participant: "Meeting participant" + meeting_participant: "Toplantı katılımcısı" member: "Üye" news: "Haberler" notification: @@ -1943,18 +1943,18 @@ tr: one: "Rol" other: "Yetkiler" scim_client: - one: "SCIM client" + one: "SCIM istemcisi" other: "SCIM istemcileri" status: "İş paketi durumu" token/api: one: Erişim simgesi other: Erişim belirteçleri token/rss: - one: "RSS token" - other: "RSS tokens" + one: "RSS Anahtarı" + other: "RSS Anahtarları" type: - one: "Type" - other: "Types" + one: "Tip" + other: "Tipler" user: "Kullanıcı" version: "Sürüm" workflow: "İş akışı" @@ -1970,10 +1970,10 @@ tr: other: "Ek olarak, aşağıdaki alanlarla ilgili bir sorun vardı:" field_erroneous_label: "Bu alan geçersiz:% {full_errors}\nLütfen geçerli bir değer girin. %{full_errors}." messages: - must_be_template: "must be template" - unsupported_storage_type: "is not a supported storage type." + must_be_template: "şablon olmalı" + unsupported_storage_type: "desteklenen bir depolama türü değildir." storage_error: "There was an error with the storage connection." - invalid_input: "The input is invalid." + invalid_input: "Giriş geçersiz." activity: item: created_by_on: "%{user} tarafından %{datetime} tarihinde oluşturuldu" @@ -2013,12 +2013,12 @@ tr: project_phase: activated: "etkinleştirildi" added_date: "%{date} tarihine ayarlandı" - changed_date: "changed from %{from} to %{to}" + changed_date: "%{from} adresinden %{to}olarak değiştirildi" deactivated: "devre dışı bırakmak" - deleted_project_phase: "Deleted project phase" + deleted_project_phase: "Silinen proje aşaması" phase_and_both_gates: "%{phase_message}. %{start_gate_message}, ve %{finish_gate_message}" phase_and_one_gate: "%{phase_message}. %{gate_message}" - removed_date: "date deleted %{date}" + removed_date: "%{date} tarihinde silindi" #common attributes of all models attributes: active: "Aktif" @@ -2058,7 +2058,7 @@ tr: estimated_time: "Çalışma" email: "E-posta" entity_type: "Varlık" - expires_at: "Expires on" + expires_at: "Süre bitişi" firstname: "İsim" filter: "Filtre" group: "Grup" @@ -2187,7 +2187,7 @@ tr: button_download: "İndir" button_disable: "Disable" button_duplicate: "Çoğalt" - button_duplicate_and_follow: "Duplicate and follow" + button_duplicate_and_follow: "Çoğaltın ve takip edin" button_edit: "Düzenle" button_enable: "Enable" button_edit_associated_wikipage: "Edit associated wiki page: %{page_title}" @@ -2413,7 +2413,7 @@ tr: units: minute_abbreviated: one: "dakika" - other: "mins" + other: "dakika" hour: one: "saat" other: "saat" @@ -2471,10 +2471,10 @@ tr: one_drive_sharepoint_file_storage: OneDrive/SharePoint Dosya Depolama placeholder_users: Yer Tutucu Kullanıcılar portfolio_management: Portfolio management - project_creation_wizard: Project initiation request + project_creation_wizard: Proje başlatma talebi project_list_sharing: Proje Listesi Paylaşımı readonly_work_packages: Salt Okunur İş Paketleri - scim_api: SCIM server API + scim_api: SCIM sunucu API'si sso_auth_providers: Tek Oturum Açma team_planner_view: Takım Planlayıcı Görünümü virus_scanning: Antivirüs Taraması @@ -2533,7 +2533,7 @@ tr: work_package_sharing: description: "İş paketlerini proje üyesi olmayan kullanıcılarla paylaşın." project_list_sharing: - description: "Share project lists with individual users." + description: "Proje listelerini belirli kullanıcılarla paylaşın." calculated_values: description: "Calculated values allow you to create a mathematical formula based attribute using numeric values and other project attributes and custom fields." define_custom_style: @@ -2544,13 +2544,13 @@ tr: title: "Özel eylemler" description: "Özel eylemler, duruma, role, türe veya projeye göre belirli iş paketlerinde kullanıma sunabileceğiniz bir dizi önceden tanımlanmış eylemin tek tıkla kısayollarıdır." mcp_server: - description: "Integrate AI agents with your OpenProject instance through MCP." + description: "Yapay zeka aracılarını MCP aracılığıyla OpenProject örneğinizle entegre edin." nextcloud_sso: title: "Single Sign-On for Nextcloud Storage" description: "Enable seamless and secure authentication for your Nextcloud storage with Single Sign-On. Simplify access management and enhance user convenience." scim_api: title: "SCIM istemcileri" - description: "Automate user management in OpenProject by seamlessly integrating external identity services like Microsoft Entra or Keycloak through our SCIM Server API. Available starting with the Enterprise corporate plan." + description: "SCIM Server API aracılığıyla Microsoft Entra veya Keycloak gibi harici kimlik servisleriyle sorunsuz entegrasyon sağlayarak OpenProject’te kullanıcı yönetimini otomatikleştirin. Sadece Kurumsal plan başlatılırsa." sso_auth_providers: title: "Tek Oturum Açma (SSO)" description: "Enable users to log in via external SSO providers using SAML or OpenID Connect for seamless access and integration with existing identity systems." @@ -2604,7 +2604,7 @@ tr: enabled: "Devre dışı bırak" enumeration_activities: "Zaman izleme faaliyetleri" enumeration_work_package_priorities: "İş paketi öncelikleri" - enumeration_reported_project_statuses: "Reported status" + enumeration_reported_project_statuses: "Bildirilen durum" enumeration_caption_order_changed: "Sıralama başarıyla değiştirildi." enumeration_could_not_be_moved: "Numaralandırma taşınamadı." enterprise_trials: @@ -2861,7 +2861,7 @@ tr: new_features_list: line_0: Automated project initiation (Enterprise add-on). line_1: "Meetings: add new or existing work packages as outcomes." - line_2: "Meetings: show iCal responses in OpenProject." + line_2: "Toplantılar: OpenProject'te iCal yanıtlarını gösterin." line_3: "Recurring meetings: duplicate agenda items to the next occurrence." line_4: "Release to Community: Attribute highlighting." line_5: Warning before opening external links in user-provided content (Enterprise add-on). @@ -2933,12 +2933,12 @@ tr: instructions_after_error: "%{signin} düğmesini tıklayarak tekrar oturum açmayı deneyebilirsiniz. Hata devam ederse, yöneticinizden yardım isteyin." menus: admin: - ai: "Artificial Intelligence (AI)" + ai: "Yapay Zeka (AI)" aggregation: "Birleştirme" api_and_webhooks: "API ve web kancaları" mail_notification: "E-posta bildirimleri" mails_and_notifications: "E-postalar ve bildirimler" - mcp_configurations: "Model Context Protocol (MCP)" + mcp_configurations: "Model Bağlam Protokolü (MCP)" quick_add: label: "Ekle…" my_account: @@ -2966,7 +2966,7 @@ tr: text_hint: "API belirteçleri, üçüncü taraf uygulamaların REST API'leri aracılığıyla bu OpenProject kurulumunuzla iletişim kurmasına olanak tanır." static_token_name: "API token" disabled_text: "API belirteçleri yönetici tarafından etkinleştirilmemiştir. Bu özelliği kullanmak için lütfen yöneticinize başvurun." - add_button: "API token" + add_button: "API anahtarı" ical: blank_description: "To add an iCalendar token, subscribe to a new or existing calendar from within the Calendar module of a project. You must have the necessary permissions." blank_title: "No iCalendar token" @@ -2994,7 +2994,7 @@ tr: removed: "OAuth client token successfully removed" unknown_integration: "Unknown" token/rss: - add_button: "RSS token" + add_button: "RSS anahtarı" blank_description: "There is no RSS token yet. You can create one using the button below." blank_title: "No RSS token" title: "RSS" @@ -3064,7 +3064,7 @@ tr: label_always_visible: "Her zaman görüntüle" label_announcement: "Duyuru" label_angular: "AngularJS" - label_app_modules: "%{app_title} modules" + label_app_modules: "%{app_title} modüller" label_api_access_key: "API erişim anahtarı" label_api_access_key_created_on: "API erişim anahtarı %{value} önce oluşturuldu" label_api_access_key_type: "API" @@ -3971,6 +3971,7 @@ tr: notice_successful_delete: "Silme başarılı." notice_successful_cancel: "İptal işlemi başarılı." notice_successful_update: "Güncelleme başarılı." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Oluşturulamadı." notice_unsuccessful_create_with_reason: "Oluşturma başarısız oldu: %{reason}" notice_unsuccessful_update: "Güncellenemedi." @@ -4036,7 +4037,7 @@ tr: permission_commit_access: "Depoya okuma / yazma erişimi (tamamlama)" permission_copy_projects: "Proje kopyalamak" permission_copy_projects_explanation: "In template projects, this permission has a secondary function, it allows the creation of new projects derived from the template." - permission_copy_work_packages: "Duplicate work packages" + permission_copy_work_packages: "İş paketlerini çoğalt" permission_create_backup: "Yedek oluşturma" permission_delete_work_package_watchers: "Takipçi sil" permission_delete_work_packages: "İş paketi silmek" @@ -4157,9 +4158,9 @@ tr: zero: > Proje ile ilgili tüm verileri kalıcı olarak silmek üzeresiniz %{name}. one: > - You are about to permanently delete all data relating to project %{name} and this subproject: + Proje ile ilgili tüm verileri kalıcı olarak silmek üzeresiniz %{name} ve bu alt proje: other: > - You are about to permanently delete all data relating to project %{name} and these subprojects: + Proje ile ilgili tüm verileri kalıcı olarak silmek üzeresiniz %{name} ve bu alt projeler: filters: project_phase: "Proje aşaması: %{phase}" project_phase_any: "Proje aşaması: Herhangi bir" @@ -4315,6 +4316,9 @@ tr: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "İlk giriş yönlendirmesi" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page @@ -4343,7 +4347,7 @@ tr: setting_apiv3_docs_enabled_instructions_html: > Dokümanlar sayfası etkinleştirildiyse, %{link} altında APIv3 belgelerinin etkileşimli bir görünümünü elde edebilirsiniz. setting_apiv3_docs_enabled_instructions_warning: > - Please be aware that enabling the API docs on a production system may expose sensitive information or result in accidental loss of data when not being careful. We recommend to only enable this setting for development purposes. + Uyarı: API dokümantasyonunu canlı ortamda etkinleştirmek güvenlik riskleri ve olası veri kaybına neden olabilir. Bu ayarı sadece geliştirme ortamlarında kullanmanızı öneririz. setting_attachment_whitelist: "Ek yükleme beyaz listesi" setting_email_delivery_method: "E-posta teslim yöntemi" setting_emails_salutation: "E-postalarda kullanıcıya şu şekilde hitap edin" @@ -4636,7 +4640,7 @@ tr: status_when_submitted_caption: "The status the generated work package will transition to once the request is submitted." send_confirmation_email: "Send confirmation email to the user who submitted the project initiation request" assignee: "Assignee when submitted" - assignee_caption_html: "The user or group assigned to this project attribute will also become the assignee of the new work package. This list includes active project attributes of type User only." + assignee_caption_html: "Bu proje niteliğine atanan kullanıcı veya grup, yeni iş paketinin de atananı olacaktır. Bu liste yalnızca User türündeki aktif proje niteliklerini içerir." confirmation_email_text: "Confirmation email text" confirmation_email_default: "Hello,\n\nYou submitted a project initiation request for **%{project_name}**. It is now awaiting review.\nClick the link below to access the work package with your request." work_package_comment: "Work package comment" diff --git a/config/locales/crowdin/uk.yml b/config/locales/crowdin/uk.yml index 1c0e64611fb..f269844d9ab 100644 --- a/config/locales/crowdin/uk.yml +++ b/config/locales/crowdin/uk.yml @@ -113,7 +113,7 @@ uk: index: description: "Протокол контексту моделі дає змогу агентам ШІ надавати своїм користувачам інструменти й ресурси, доступні в цьому екземплярі OpenProject." resources_heading: "Ресурси" - resources_description: "OpenProject реалізує наведені нижче інструменти. Кожен із них можна ввімкнути, перейменувати й описати як завгодно. Щоб дізнатися більше, ознайомтеся з [документацією щодо ресурсів MCP] (docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Оновити ресурси" tools_heading: "Інструменти" tools_description: "OpenProject реалізує наведені нижче інструменти. Кожен із них можна ввімкнути, перейменувати й описати як завгодно. Щоб дізнатися більше, ознайомтеся з [документацією щодо інструментів MCP] (docs_url)." @@ -122,7 +122,7 @@ uk: success: "Конфігурації MCP оновлено." server_form: description_caption: "Опис сервера MCP для інших додатків, які підключаються до нього." - title_caption: "Короткий заголовок, який відображається для додатків, що підключаються до сервера MCP." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "Не вдалось оновити конфігурацію MCP." success: "Конфігурацію MCP оновлено." @@ -4065,6 +4065,7 @@ uk: notice_successful_delete: "Видалення успішно завершене." notice_successful_cancel: "Успішно видалено." notice_successful_update: "Успішно оновлено." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Не вдалося створити." notice_unsuccessful_create_with_reason: "Не вдалося створити: %{reason}" notice_unsuccessful_update: "Не вдалось оновити." @@ -4411,6 +4412,9 @@ uk: setting_capture_external_links: "Захоплення зовнішніх посилань" setting_capture_external_links_text: > Якщо ввімкнено, усі зовнішні посилання у відформатованому тексті переспрямовуватимуть на попереджувальну сторінку перед переходом із додатка. Це допомагає захистити користувачів від потенційно шкідливих зовнішніх вебсайтів. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "Переспрямування після першого входу" setting_after_first_login_redirect_url_text_html: > Задайте шлях для переспрямування користувачів після першого входу. Якщо не задано, користувачі переспрямовуються на головну сторінку з ознайомленням.
    Наприклад: /my/page diff --git a/config/locales/crowdin/uz.yml b/config/locales/crowdin/uz.yml index f6bbbb0a865..05311a3730e 100644 --- a/config/locales/crowdin/uz.yml +++ b/config/locales/crowdin/uz.yml @@ -113,7 +113,7 @@ uz: index: description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." resources_heading: "Resources" - resources_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Update resources" tools_heading: "Tools" tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." @@ -122,7 +122,7 @@ uz: success: "MCP configurations were updated successfully." server_form: description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applicatons that connect to the MCP server." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP configuration could not be updated." success: "MCP configuration was updated successfully." @@ -3972,6 +3972,7 @@ uz: notice_successful_delete: "Successful deletion." notice_successful_cancel: "Successful cancellation." notice_successful_update: "Successful update." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Creation failed." notice_unsuccessful_create_with_reason: "Creation failed: %{reason}" notice_unsuccessful_update: "Update failed." @@ -4317,6 +4318,9 @@ uz: setting_capture_external_links: "Capture external links" setting_capture_external_links_text: > When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "First login redirect" setting_after_first_login_redirect_url_text_html: > Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
    Example: /my/page diff --git a/config/locales/crowdin/vi.yml b/config/locales/crowdin/vi.yml index 432ed949a43..bef9ae26544 100644 --- a/config/locales/crowdin/vi.yml +++ b/config/locales/crowdin/vi.yml @@ -113,7 +113,7 @@ vi: index: description: "Giao thức bối cảnh mô hình cho phép các tác nhân AI cung cấp cho người dùng của mình các công cụ và tài nguyên được cung cấp bởi phiên bản OpenProject này." resources_heading: "Tài nguyên" - resources_description: "OpenProject cung cấp các công cụ sau đây. Mỗi công cụ có thể được kích hoạt, đổi tên và mô tả theo ý muốn của bạn. Để biết thêm thông tin, vui lòng tham khảo [tài liệu về tài nguyên MCP](docs_url)." + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "Cập nhật tài nguyên" tools_heading: "Công cụ" tools_description: "OpenProject cung cấp các công cụ sau đây. Mỗi công cụ có thể được kích hoạt, đổi tên và mô tả theo ý muốn của bạn. Để biết thêm thông tin, vui lòng tham khảo [tài liệu về các công cụ MCP](docs_url)." @@ -122,7 +122,7 @@ vi: success: "Cấu hình MCP đã được cập nhật thành công." server_form: description_caption: "Cách máy chủ MCP sẽ được mô tả cho các ứng dụng khác kết nối với nó." - title_caption: "Một tiêu đề ngắn được hiển thị cho các ứng dụng kết nối với máy chủ MCP." + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "Không thể cập nhật cấu hình MCP." success: "Cấu hình MCP đã được cập nhật thành công." @@ -3920,6 +3920,7 @@ vi: notice_successful_delete: "Xóa thành công." notice_successful_cancel: "Hủy thành công." notice_successful_update: "Cập nhật thành công." + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "Tạo không thành công." notice_unsuccessful_create_with_reason: "Tạo không thành công: %{reason}" notice_unsuccessful_update: "Cập nhật không thành công." @@ -4264,6 +4265,9 @@ vi: setting_capture_external_links: "Chụp các liên kết bên ngoài" setting_capture_external_links_text: > Khi tính năng này được kích hoạt, tất cả các liên kết ngoài trong văn bản định dạng sẽ được chuyển hướng qua một trang cảnh báo trước khi rời khỏi ứng dụng. Điều này giúp bảo vệ người dùng khỏi các trang web bên ngoài có thể chứa mã độc hại. + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "Chuyển hướng đăng nhập lần đầu" setting_after_first_login_redirect_url_text_html: > Đặt đường dẫn để chuyển hướng người dùng sau lần đăng nhập đầu tiên của họ. Nếu trống, hãy chuyển hướng đến trang chủ để xem chuyến tham quan giới thiệu.
    Ví dụ: /my/page diff --git a/config/locales/crowdin/zh-CN.yml b/config/locales/crowdin/zh-CN.yml index 9b241c98a32..918d9eb3148 100644 --- a/config/locales/crowdin/zh-CN.yml +++ b/config/locales/crowdin/zh-CN.yml @@ -113,7 +113,7 @@ zh-CN: index: description: "Model Context Protocol 允许 AI 智能体向其用户提供此 OpenProject 实例所公开的工具和资源。" resources_heading: "资源" - resources_description: "OpenProject 实现了以下工具。每种工具都可以根据需要启用、重命名和描述。有关详情,请参阅[关于 MCP 资源的文档](docs_url)。" + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "更新资源" tools_heading: "工具" tools_description: "OpenProject 实现了以下工具。每种工具都可以根据需要启用、重命名和描述。有关详情,请参阅[关于 MCP 工具的文档](docs_url)。" @@ -122,7 +122,7 @@ zh-CN: success: "MCP 配置已成功更新。" server_form: description_caption: "如何向连接到 MCP 服务器的其他应用程序描述该 MCP 服务器。" - title_caption: "展示给连接到 MCP 服务器的应用程序的简短标题。" + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "MCP 配置无法更新。" success: "MCP 配置已成功更新。" @@ -3916,6 +3916,7 @@ zh-CN: notice_successful_delete: "成功删除。" notice_successful_cancel: "取消成功" notice_successful_update: "成功更新。" + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "创建失败。" notice_unsuccessful_create_with_reason: "创建失败:%{reason}" notice_unsuccessful_update: "更新失败。" @@ -4257,6 +4258,9 @@ zh-CN: setting_capture_external_links: "捕获外部链接" setting_capture_external_links_text: > 启用后,格式化文本中的所有外部链接在离开应用程序前都会重定向至警告页面。这有助于保护用户免受潜在恶意外部网站的危害。 + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "首次登录重定向" setting_after_first_login_redirect_url_text_html: > 设置用户首次登录后的重定向路径。如果该路径为空,则重定向到主页以进行导览介绍。
    示例: /my/page diff --git a/config/locales/crowdin/zh-TW.yml b/config/locales/crowdin/zh-TW.yml index f7e8edb22c1..92d8d5a3536 100644 --- a/config/locales/crowdin/zh-TW.yml +++ b/config/locales/crowdin/zh-TW.yml @@ -113,7 +113,7 @@ zh-TW: index: description: "模型上下文協定允許 AI 代理向其使用者提供此 OpenProject 實體所揭露的工具與資源。" resources_heading: "資源" - resources_description: "OpenProject 實作下列工具。每個工具都可以依您的需求啟用、重新命名和描述。如需詳細資訊,請參閱 [MCP 資源文件](docs_url)。" + resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." resources_submit: "更新資源" tools_heading: "工具" tools_description: "OpenProject 實作下列工具。每個工具都可以依您的需求啟用、重新命名和描述。如需詳細資訊,請參閱 [MCP 工具說明文件](docs_url)。" @@ -122,7 +122,7 @@ zh-TW: success: "MCP 組態已成功更新。" server_form: description_caption: "MCP 伺服器將如何描述給連線到它的其他應用程式。" - title_caption: "顯示給與 MCP 伺服器連線的應用程式的簡短標題。" + title_caption: "A short title shown to applications that connect to the MCP server." update: failure: "無法更新 MCP 設定。" success: "MCP 組態已成功更新。" @@ -606,7 +606,7 @@ zh-TW: is_for_all_blank_slate: heading: 對所有的專案 description: 由於已勾選「適用於所有專案」選項,因此所有專案都啟用此專案屬性。個別專案無法停用。 - enabled_via_assignee_when_submitted_html: This project attribute cannot be disabled since it is set as assignee when submitted for project initiation requests. + enabled_via_assignee_when_submitted_html: 此專案屬性無法停用,因為它已被設定為專案啟動請求提交時的 指派人。 types: no_results_title_text: 目前沒有可用的類型 form: @@ -634,7 +634,7 @@ zh-TW: label_request_submission: "要求提交" project_attributes_description: > 選擇哪些專案屬性應包含在專案啟動請求中。此清單僅包含此專案啟用的 [專案屬性](project_attributes_url)。 - enabled_because_required_html: This project attribute cannot be disabled for this project initiation request since it is defined as required. This can be changed in the administration settings by the administrator of the instance. + enabled_because_required_html: 由於此專案屬性被定義為必填項目,因此無法在此專案啟動請求中停用。此設定可由系統管理員於 管理設定 中進行變更。 status: button_edit: 編輯狀態 wizard: @@ -3917,6 +3917,7 @@ zh-TW: notice_successful_delete: "刪除成功" notice_successful_cancel: "取消成功" notice_successful_update: "更新成功" + notice_successful_move: "Successful move from %{from} to %{to}." notice_unsuccessful_create: "建立失敗。" notice_unsuccessful_create_with_reason: "建立失敗:%{reason}" notice_unsuccessful_update: "更新失敗。" @@ -4260,6 +4261,9 @@ zh-TW: setting_capture_external_links: "擷取外部連結" setting_capture_external_links_text: > 啟用後,格式化文字中的所有外部連結都會在離開應用程式前透過警告頁重定向。這有助於保護使用者遠離潛在的惡意外部網站。 + setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login_text: > + When enabled, users wanting to click on external links need to be logged in before being able to continue. setting_after_first_login_redirect_url: "首次登入重新導向" setting_after_first_login_redirect_url_text_html: > 設定使用者首次登入後的重新導向路徑。如果為空,則會重定向到上線導覽的首頁。
    範例:/my/page diff --git a/modules/auth_saml/config/locales/crowdin/tr.yml b/modules/auth_saml/config/locales/crowdin/tr.yml index b03773ac625..0f94f19f9b9 100644 --- a/modules/auth_saml/config/locales/crowdin/tr.yml +++ b/modules/auth_saml/config/locales/crowdin/tr.yml @@ -6,7 +6,7 @@ tr: identifier: Tanımlayıcı secret: Gizli scope: Kapsam - assertion_consumer_service_url: ACS (Assertion tüketici hizmeti) URL'si + assertion_consumer_service_url: ACS (Kimlik Doğrulama hizmeti) URL'si limit_self_registration: Kendi kendine kaydı sınırlayın sp_entity_id: Hizmet kuruluşu kimliği metadata_url: Kimlik sağlayıcı meta veri URL'si @@ -71,7 +71,7 @@ tr: request_attributes: title: 'Talep edilen nitelikler' legend: > - These attributes are added to the SAML XML metadata to signify to the identify provider which attributes OpenProject requires. You may still need to explicitly configure your identity provider to send these attributes. Please refer to your IdP's documentation. + Bu öznitelikler (attributes), OpenProject’in ihtiyaç duyduğu özniteliklerin kimlik sağlayıcı (Identity Provider – IdP) tarafından anlaşılabilmesi amacıyla SAML XML meta verisine eklenmektedir. Bununla birlikte, ilgili özniteliklerin IdP tarafından gönderilebilmesi için kimlik sağlayıcı üzerinde ayrıca açık bir yapılandırma yapılması gerekebilir. Ayrıntılı yapılandırma adımları için lütfen IdP dokümantasyonunu inceleyiniz. name: 'İstenen öznitelik anahtarı' format: 'Öznitelik biçimi' section_headers: @@ -79,78 +79,78 @@ tr: attributes: "Özellikler" section_texts: display_name: "SAML sağlayıcısının görünen adını değiştir." - metadata: "Pre-fill configuration using a metadata URL or by pasting metadata XML" - metadata_form: "If your identity provider has a metadata endpoint or XML download, add it below to pre-fill the configuration." + metadata: "Bir meta veri URL’si kullanarak ya da meta veri XML’ini manuel olarak yapıştırarak yapılandırmayı otomatik olarak önceden doldurun." + metadata_form: "Kimlik sağlayıcınız bir meta veri uç noktası veya meta veri XML indirme seçeneği sunuyorsa, yapılandırmanın önceden doldurulabilmesi için ilgili bilgiyi aşağıya girin." metadata_form_banner: "Metaverileri düzenlemek diğer bölümlerde var olan değerlerin üstüne yazılmasına neden olabilir. " - configuration: "Configure the endpoint URLs for the identity provider, certificates, and further SAML options." - configuration_metadata: "This information has been pre-filled using the supplied metadata. In most cases, they do not require editing." - encryption: "Configure assertion signatures and encryption for SAML requests and responses." - encryption_form: "You may optionally want to encrypt the assertion response, or have requests from OpenProject signed." + configuration: "Kimlik sağlayıcının uç nokta URL’lerini, sertifika ayarlarını ve diğer SAML yapılandırma seçeneklerini tanımlayın." + configuration_metadata: "Bu bilgiler, girilen meta verilerden otomatik olarak alınarak doldurulmuştur ve genellikle manuel düzenleme gerektirmez." + encryption: "SAML istekleri ve yanıtlarında kullanılan assertion imzalama ve şifreleme yapılandırmalarını tanımlayın." + encryption_form: "Gereksinimlerinize bağlı olarak doğrulama yanıtlarının şifrelenmesini etkinleştirebilir ya da OpenProject kaynaklı isteklerin dijital olarak imzalanmasını yapılandırabilirsiniz." mapping: "SAML yanıtı ile OpenProject'teki kullanıcı öznitelikleri arasındaki eşlemeyi elle ayarlayın." - requested_attributes: "Define the set of attributes to be requested in the SAML request sent to your identity provider." - seeded_from_env: "This provider was seeded from the environment configuration. It cannot be edited." + requested_attributes: "Kimlik sağlayıcınıza gönderilen SAML isteğinde talep edilecek öznitelik kümesini tanımlayın." + seeded_from_env: "Bu kimlik sağlayıcı, ortam yapılandırması üzerinden ön tanımlı olarak oluşturulmuştur ve değiştirilemez." settings: - metadata_none: "I don't have metadata" + metadata_none: "Meta verilerim yok" metadata_url: "Metaveri URL'si" - metadata_xml: "Metadata XML" + metadata_xml: "Metaveri XML" instructions: documentation_link: > - Please refer to our [documentation on configuring SAML providers](docs_url) for more information on these configuration options. + Bu yapılandırma seçenekleri hakkında daha fazla bilgi için lütfen [SAML sağlayıcılarını yapılandırma belgeleri] (docs_url) bakın. display_name: > Sağlayıcının adı. Bu, oturum açma düğmesi olarak ve sağlayıcılar listesinde görüntülenecektir. metadata_none: > - Your identity provider does not have a metadata endpoint or XML download option. You can configure it manually. + metadata_url: > - Your identity provider provides a metadata URL. + Kimlik sağlayıcınız bir meta veri URL'si sağlar. metadata_xml: > - Your identity provider provides a metadata XML download. + Kimlik sağlayıcınız bir meta veri XML indirmesi sağlar. limit_self_registration: > Etkinleştirilirse, kullanıcılar bu sağlayıcıyı kullanarak yalnızca kendi kendine kayıt ayarı buna izin veriyorsa kaydolabilir. sp_entity_id: > - The entity ID of the service provider (SP). Sometimes also referred to as Audience. This is the unique client identifier of the OpenProject instance. + Hizmet sağlayıcının (SP) varlık kimliği. Bazen İzleyici olarak da adlandırılır. Bu, OpenProject örneğinin benzersiz istemci tanımlayıcısıdır. idp_sso_service_url: > - The URL of the identity provider login endpoint. + Kimlik sağlayıcı oturum açma uç noktasının URL'si. idp_slo_service_url: > - The URL of the identity provider logout endpoint. + Kimlik sağlayıcı oturum kapatma uç noktasının URL'si. idp_cert: > - Enter the X509 PEM-formatted public certificate of the identity provider. You can enter multiple certificates by separating them with a newline. + Kimlik sağlayıcının X.509 PEM biçimindeki genel anahtar sertifikasını giriniz. Birden fazla sertifika, satır sonu kullanılarak ayrı ayrı tanımlanabilir. name_identifier_format: > - Set the name identifier format to be used for the SAML assertion. + SAML doğrulaması içerisinde kullanılacak formatını tanımlayın. sp_metadata_endpoint: > - This is the URL where the OpenProject SAML metadata is available. Optionally use it to configure your identity provider. + Bu, OpenProject SAML meta verilerinin mevcut olduğu URL'dir. İsteğe bağlı olarak kimlik sağlayıcınızı yapılandırmak için kullanın. mapping: > - Configure the mapping between the SAML response and user attributes in OpenProject. You can configure multiple attribute names to look for. OpenProject will choose the first available attribute from the SAML response. + SAML yanıtı ile OpenProject’teki kullanıcı öznitelikleri arasındaki eşlemeyi yapılandırın. Birden fazla öznitelik adını arama kriteri olarak tanımlayabilirsiniz. OpenProject, SAML yanıtında bulunan ilk geçerli özniteliği kullanacaktır. mapping_login: > - SAML attributes from the response used for the login. + Oturum açma işlemi için kullanılan SAML yanıtı öznitelikleri. mapping_mail: > - SAML attributes from the response used for the email of the user. + Kullanıcının e-posta adresi için kullanılan SAML yanıtı öznitelikleri. mapping_firstname: > - SAML attributes from the response used for the given name. + Kullanıcının ad bilgisinin alınmasında kullanılan SAML yanıtındaki öznitelikler. mapping_lastname: > - SAML attributes from the response used for the last name. + Kullanıcının soyad bilgisinin alınmasında kullanılan SAML yanıtındaki öznitelikler. mapping_uid: > - SAML attribute to use for the internal user ID. Leave empty to use the name_id attribute instead + Dahili kullanıcı kimliği için kullanılacak SAML özniteliği. Bunun yerine name_id özniteliğini kullanmak için boş bırakın request_uid: > - SAML attribute to request for the internal user ID. By default, the name_id will be used for this field. + Dahili kullanıcı kimliği için talep edilecek SAML özniteliği. Varsayılan olarak bu alan için name_id kullanılacaktır. requested_attributes: > - These attributes are added to the SAML request XML to communicate to the identity provider which attributes OpenProject requires. + requested_format: > - The format of the requested attribute. This is used to specify the format of the attribute in the SAML request. Please see [documentation on configuring requested attributes](docs_url) for more information. + İstenen özniteliğin biçimi. Bu, SAML isteğindeki özniteliğin biçimini belirtmek için kullanılır. Daha fazla bilgi için lütfen [istenen öznitelikleri yapılandırma belgelerine](docs_url) bakın. authn_requests_signed: > - If checked, OpenProject will sign the SAML AuthnRequest. You will have to provide a signing certificate and private key using the fields below. + İşaretlenirse, OpenProject SAML AuthnRequest'i imzalayacaktır. Aşağıdaki alanları kullanarak bir imzalama sertifikası ve özel anahtar sağlamanız gerekecektir. want_assertions_signed: > - If checked, OpenProject will required signed responses from the identity provider using its own certificate keypair. OpenProject will verify the signature against the certificate from the basic configuration section. + İşaretlenirse, OpenProject kendi sertifika anahtar çiftini kullanarak kimlik sağlayıcıdan imzalı yanıtlar isteyecektir. OpenProject imzayı temel yapılandırma bölümündeki sertifikaya göre doğrulayacaktır. want_assertions_encrypted: > - If enabled, require the identity provider to encrypt the assertion response using the certificate pair that you provide. + Etkinleştirilirse, kimlik sağlayıcısının sağladığınız sertifika çiftini kullanarak onay yanıtını şifrelemesini gerektirir. certificate: > OpenProject tarafından SAML isteklerini imzalamak için kullanılacak X509 PEM biçimli sertifikayı girin. private_key: > - Enter the X509 PEM-formatted private key for the above certificate. This needs to be an RSA private key. + Yukarıdaki sertifika için X509 PEM biçimli özel anahtarı girin. Bunun bir RSA özel anahtarı olması gerekir. signature_method: > - Select the signature algorithm to use for the SAML request signature performed by OpenProject (Default: %{default_option}). + OpenProject tarafından gerçekleştirilen SAML istek imzası için kullanılacak imza algoritmasını seçin (Varsayılan: %{default_option}). digest_method: > - Select the digest algorithm to use for the SAML request signature performed by OpenProject (Default: %{default_option}). + OpenProject tarafından gerçekleştirilen SAML istek imzası için kullanılacak özet algoritmasını seçin (Varsayılan: %{default_option}). icon: > - Optionally provide a public URL to an icon graphic that will be displayed next to the provider name. + İsteğe bağlı olarak, sağlayıcı adının yanında görüntülenecek bir simge grafiği için genel bir URL sağlayın. metadata_for_idp: > - This information might be requested by your SAML identity provider. + Bu bilgiler SAML kimlik sağlayıcınız tarafından talep edilebilir. diff --git a/modules/backlogs/config/locales/crowdin/af.yml b/modules/backlogs/config/locales/crowdin/af.yml index b7844767955..8a8df3394e6 100644 --- a/modules/backlogs/config/locales/crowdin/af.yml +++ b/modules/backlogs/config/locales/crowdin/af.yml @@ -25,6 +25,8 @@ af: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Posisie" story_points: "Storie Punte" @@ -43,128 +45,97 @@ af: attributes: task_type: "Task type" backlogs: - add_new_story: "Nuwe storie" any: "eenige" - backlog_settings: "Agterstand instellings" - burndown_graph: "Afbrand Grafiek" - card_paper_size: "Papier grootte vir kaart drukwerk" - chart_options: "Grafiek opsies" - close: "Maak toe" - column_width: "Kolom breedte:" - date: "Dag" + column_width: "Column width" definition_of_done: "Definisie van Gedoen" - generating_chart: "Genereer Grafiek..." - hours: "Ure" impediment: "Belemmering" label_versions_default_fold_state: "Wys weergawe gevou" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." work_package_is_closed: "Werk pakket is klaar, wanneer" label_is_done_status: "Status %{status_name} beteken klaar" - no_burndown_data: "Geen afbrand data beskikbaar nie. Dit is noodig om die sprint begin- end uindig- datum to stel." - points: "Punte" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "Posisies kon nie herbou word nie." positions_rebuilt_successfully: "Posisies was suksesvol herbou." - properties: "Eienskappe" rebuild: "Herbou" rebuild_positions: "Herbou posisies" remaining_hours: "Oorblywende werk" - remaining_hours_ideal: "Oorblywende werk (ideale)" show_burndown_chart: "Afbrand Grafiek" story: "Storie" - story_points: "Storie Punte" - story_points_ideal: "Storie Punte (ideale)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "Taak" task_color: "Taak kleure" unassigned: "Ongetekende" user_preference: header_backlogs: "Backlogs module" button_update_backlogs: "Update backlogs module" - x_more: "%{count} meer..." - backlogs_active: "aktief" - backlogs_any: "eenige" - backlogs_inactive: "Projek wys geen aktiwiteit nie" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Op/Af brand punte" backlogs_product_backlog: "Produk agterstand" - backlogs_product_backlog_is_empty: "Produk agterstand is leeg" - backlogs_product_backlog_unsized: "Die top van die produk-agterstand het stories wat nie n grote het nie" - backlogs_sizing_inconsistent: "Storiegroottes verskil teen hul skattings" - backlogs_sprint_notes_missing: "Geslote sprinte sonder retrospektief/hersiening notas" - backlogs_sprint_unestimated: "Geslote of aktiewe sprinte met ongeskatte stories" - backlogs_sprint_unsized: "Projek het stories op aktiewe of onlangs geslote sprinte wat nie n grootte gehaad het nie" - backlogs_sprints: "Sprinte" backlogs_story: "Storie" backlogs_story_type: "Storie tipes" backlogs_task: "Taak" backlogs_task_type: "Taak tipes" - backlogs_velocity_missing: "Geen snelheid kon vir hierdie projek bereken word nie" - backlogs_velocity_varies: "Snelheid wissel aansienlik oor sprinte" backlogs_wiki_template: "Templaat vir sprint wiki bladsy" - backlogs_empty_title: "Geen weergawes is gedefinieer vir gebruik in die agterstande nie" - backlogs_empty_action_text: "Om met agterstande te begin, skep 'n nuwe weergawe en ken dit toe aan 'n agterstandkolom." - button_edit_wiki: "Wysig wiki-bladsy" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "Die volgende foute was teegekom:" - error_intro_singular: "Die volgende fout was teegekom:" - error_outro: "Korrigeer asseblief die bogenoemde foute voordat u weer indien." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ideale" - inclusion: "is nie by die lys ingesluit nie" - label_back_to_project: "Terug na projekbladsy" - label_backlog: "Agterstand" label_backlogs: "Agterstand" label_backlogs_unconfigured: "Jy het nog nie Agterstandes opgestel nie. Gaan asseblief na %{administration} > %{plugins}, klik dan op die %{configure}-skakel vir hierdie plugin. Sodra jy die velde gestel het, kom terug na hierdie bladsy om die instrument te begin gebruik." label_blocks_ids: "ID's van geblokkeerde werkspakkette" - label_burndown: "Afbrand" label_column_in_backlog: "Kolom in agterstand" - label_hours: "ure" - label_work_package_hierarchy: "Werkspakket Hiërargie" - label_master_backlog: "Meester Agterstand" - label_not_prioritized: "nie geprioritiseer nie" - label_points: "Punte" label_points_burn_down: "Af" label_points_burn_up: "Op" - label_product_backlog: "Produk agterstand" - label_select_all: "Kies almal" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "sprint agterstand" - label_sprint_cards: "Voer kaarte uit" label_sprint_impediments: "Sprint belemmerings" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Snelheid %{velocity}, gebaseer op %{sprints} sprinte met 'n gemiddeld van %{days} dae" - label_stories: "Stories" - label_stories_tasks: "Stories/Take" label_task_board: "Taak bord" - label_version_setting: "Weergawes" - label_version: 'Weergawe' - label_webcal: "Webkal Voer" - label_wiki: "Wiki" permission_view_master_backlog: "Kyk na meester-agterstand" permission_view_taskboards: "Kyk na taakborde" permission_select_done_statuses: "Kies gedoen statusse" permission_update_sprints: "Opdateer sprinte" - points_accepted: "punte aanvaar" - points_committed: "punte toegewyd" - points_resolved: "punte opgelos" - points_to_accept: "punte nie aanvaar nie" - points_to_resolve: "punte wat nie opgelos is nie" project_module_backlogs: "Agterstandes" - rb_label_copy_tasks: "Kopieer werkpakkette" - rb_label_copy_tasks_all: "Almal" - rb_label_copy_tasks_none: "Geen" - rb_label_copy_tasks_open: "Oop" - rb_label_link_to_original: "Sluit skakel na oorspronklike storie in" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "Oorblywende werk" - required_burn_rate_hours: "vereiste brandtempo (ure)" - required_burn_rate_points: "vereiste brandtempo (punte)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Kolom in agterstand" version_settings_display_option_left: "links" version_settings_display_option_none: "geen" diff --git a/modules/backlogs/config/locales/crowdin/ar.yml b/modules/backlogs/config/locales/crowdin/ar.yml index 09024391eec..bba9490e754 100644 --- a/modules/backlogs/config/locales/crowdin/ar.yml +++ b/modules/backlogs/config/locales/crowdin/ar.yml @@ -25,6 +25,8 @@ ar: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "الموقع" story_points: "نقاط القصة" @@ -43,128 +45,105 @@ ar: attributes: task_type: "Task type" backlogs: - add_new_story: "قصة جديدة" any: "أي" - backlog_settings: "إعدادات السجلات المتراكمة" - burndown_graph: "الرسم البياني لتقدم العمل" - card_paper_size: "حجم الورق لطباعة البطاقة" - chart_options: "خيارات الرسم البياني" - close: "أغلِق" - column_width: "عرض العمود:" - date: "اليوم" + column_width: "Column width" definition_of_done: "تعريف ما تم" - generating_chart: "إنشاء رسم بياني..." - hours: "الساعات" impediment: "عائق" label_versions_default_fold_state: "إظهار الإصدارات مطوية" caption_versions_default_fold_state: "" work_package_is_closed: "مجموعة العمل قد تمت، عندما" label_is_done_status: "الحالة %{status_name} تعني أنها منجزة" - no_burndown_data: "لا يوجد معلومات متوفرة عن الاستهلاك. من الضروري تحديد تواريخ البدء- والانتهاء في السباق." - points: "النقاط" + points_label: + zero: "points" + one: "point" + two: "points" + few: "points" + many: "points" + other: "points" positions_could_not_be_rebuilt: "تعذَر إعادة بناء المواقع." positions_rebuilt_successfully: "تم إعادة بناء المواقع بنجاح." - properties: "الخصائص" rebuild: "إعِدْ بناء" rebuild_positions: "أعِد بناء المواقع" remaining_hours: "" - remaining_hours_ideal: "الساعات المتبقية (المثالية)" show_burndown_chart: "الرسم البياني للعمل المتبقي" story: "القصة" - story_points: "نقاط القصة" - story_points_ideal: "نقاط القصة (المثالي)" + story_points: + zero: "%{count} story points" + one: "%{count} story point" + two: "%{count} story points" + few: "%{count} story points" + many: "%{count} story points" + other: "%{count} story points" task: "المهمة" task_color: "لون المهمّة" unassigned: "غير المعيّنة" user_preference: header_backlogs: "" button_update_backlogs: "Update backlogs module" - x_more: "%{count} أكثر..." - backlogs_active: "نشِط" - backlogs_any: "أي" - backlogs_inactive: "لا يُظهر المشروع أي نشاط" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "نقاط الاستهلاك الإيجابي/ السلبي" backlogs_product_backlog: "عمل المنتج المتراكم غير المنجز" - backlogs_product_backlog_is_empty: "العمل المتراكم غير المنجز للمنتج فارغ" - backlogs_product_backlog_unsized: "الجزء الأعلى للعمل المتراكم غير المنجز للمنتج له قصص غير محددة" - backlogs_sizing_inconsistent: "تتفاوت أحجام القصة ضد تقديراتها" - backlogs_sprint_notes_missing: "سباقات مغلقة دون أثر رجعي/استعراض الملاحظات" - backlogs_sprint_unestimated: "سباقات مغلقة أو نشطة مع قصص غير مقدّرة" - backlogs_sprint_unsized: "للمشروع قصص في سباقات نشطة أو مغلقة حديثًا لم يتم تحديد حجمها" - backlogs_sprints: "السباقات" backlogs_story: "القصة" backlogs_story_type: "أنواع القصة" backlogs_task: "المهمة" backlogs_task_type: "نوع المهمة" - backlogs_velocity_missing: "لا يمكن احتساب السرعة في هذا المشروع" - backlogs_velocity_varies: "السرعة تتفاوت بشكل ملحوظ على السباقات" backlogs_wiki_template: "نموذج لصفحة ويكي wiki الخاصة بالسباق" - backlogs_empty_title: "لا توجد إصدارات محددة لاستخدامها في قائمة الأعمال" - backlogs_empty_action_text: "للبدء مع قائمة الأعمال ، قم بإنشاء إصدار جديد و تعيينه إلى عمود قائمة الأعمال." - button_edit_wiki: "عدّل صفحة ويكي wiki" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "تم مصادفة الأخطاء التالية:" - error_intro_singular: "تمت مصادفة الخطأ التالي:" - error_outro: "من فضلك صحح الأخطاء في الأعلى قبل التقديم مرة أخرى." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "مثالي" - inclusion: "لم يتم تضمينه في القائمة" - label_back_to_project: "العودة إلى صفحة المنتج" - label_backlog: "العمل المتراكم غير المنجز" label_backlogs: "الأعمال المتراكمة غير المنجزة" label_backlogs_unconfigured: "لم تقم بإنشاء الأعمال المتراكمة غير المنجزة بعد. من فضلك اذهب إلى %{administration} > %{plugins}، ثم اضغط على رابط %{configure} لهذا البرنامج المساعد. عندما تنتهي من تعيين الحقول، ارجع إلى هذه الصفحة لتبدأ باستخدام الأداة." label_blocks_ids: "الهويات المعرِّفة لمجموعات العمل المحظورة" - label_burndown: "العمل المتبقي" label_column_in_backlog: "عمود في العمل المتراكم غير المنجز" - label_hours: "الساعات" - label_work_package_hierarchy: "التسلسل الهرمي لمجموعة العمل" - label_master_backlog: "العمل الرئيسي المتراكم غير المنجز" - label_not_prioritized: "لم يُعطى له الأولوية" - label_points: "النقاط" label_points_burn_down: "الأسفل" label_points_burn_up: "الأعلى" - label_product_backlog: "منتج متراكم غير منجز" - label_select_all: "اختر الجميع" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "سباق متراكم غير منجز" - label_sprint_cards: "بطاقات التصدير" label_sprint_impediments: "عوائق السباق" - label_sprint_name: "السباق \"%{name}\"" - label_sprint_velocity: "السرعة %{velocity}، استنادًا على %{sprints} السباقات بمتوسط %{days} الأيام" - label_stories: "القصص" - label_stories_tasks: "القصص/ المهمات" label_task_board: "لوحة المهمة" - label_version_setting: "الإصدارات" - label_version: 'النسخة' - label_webcal: "تغذية Webcal" - label_wiki: "Wiki" permission_view_master_backlog: "عرض العمل الرئيسي المتراكم غير المنجز" permission_view_taskboards: "شاهد لوحات المهمات" permission_select_done_statuses: "حدد حالات الاتمام" permission_update_sprints: "قم بتحديث السباقات" - points_accepted: "النقاط التي تم قبولها" - points_committed: "النقاط التي تم إحرازها" - points_resolved: "النقاط التي تم حلّها" - points_to_accept: "النقاط التي لم يتم قبولها" - points_to_resolve: "النقاط التي لم يتم حلّها" project_module_backlogs: "الأعمال المتراكمة غير المنجزة" - rb_label_copy_tasks: "انسخ مجموعات العمل" - rb_label_copy_tasks_all: "الجميع" - rb_label_copy_tasks_none: "لا شيء" - rb_label_copy_tasks_open: "مفتوح" - rb_label_link_to_original: "ضمّن الرابط للقصة الأصلية" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "الساعات المتبقية" - required_burn_rate_hours: "معدّل الاستهلاك المطلوب (الساعات)" - required_burn_rate_points: "معدّل الاستهلاك المطلوب (النقاط)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "عمود في العمل المتراكم غير المنجز" version_settings_display_option_left: "اليسار" version_settings_display_option_none: "لا شيء" diff --git a/modules/backlogs/config/locales/crowdin/az.yml b/modules/backlogs/config/locales/crowdin/az.yml index 3f08f5e38c5..beefe86de19 100644 --- a/modules/backlogs/config/locales/crowdin/az.yml +++ b/modules/backlogs/config/locales/crowdin/az.yml @@ -25,6 +25,8 @@ az: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Vəzifə" story_points: "" @@ -43,128 +45,97 @@ az: attributes: task_type: "Task type" backlogs: - add_new_story: "New Story" any: "any" - backlog_settings: "Backlogs settings" - burndown_graph: "Burndown Graph" - card_paper_size: "Paper size for card printing" - chart_options: "Chart options" - close: "Bağla" - column_width: "Column width:" - date: "Day" + column_width: "Column width" definition_of_done: "Definition of Done" - generating_chart: "Generating Graph..." - hours: "Hours" impediment: "Impediment" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." work_package_is_closed: "Work package is done, when" label_is_done_status: "%{status_name}" - no_burndown_data: "No burndown data available. It is necessary to have the sprint start- and end dates set." - points: "Points" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "Positions could not be rebuilt." positions_rebuilt_successfully: "Positions rebuilt successfully." - properties: "Properties" rebuild: "Rebuild" rebuild_positions: "Rebuild positions" remaining_hours: "Remaining work" - remaining_hours_ideal: "Remaining work (ideal)" show_burndown_chart: "Burndown Chart" story: "Story" - story_points: "Story Points" - story_points_ideal: "Story Points (ideal)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "Task" task_color: "Task color" unassigned: "Unassigned" user_preference: header_backlogs: "Backlogs module" button_update_backlogs: "Update backlogs module" - x_more: "%{count}..." - backlogs_active: "active" - backlogs_any: "any" - backlogs_inactive: "Project shows no activity" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Points burn up/down" backlogs_product_backlog: "Product backlog" - backlogs_product_backlog_is_empty: "Product backlog is empty" - backlogs_product_backlog_unsized: "The top of the product backlog has unsized stories" - backlogs_sizing_inconsistent: "Story sizes vary against their estimates" - backlogs_sprint_notes_missing: "Closed sprints without retrospective/review notes" - backlogs_sprint_unestimated: "Closed or active sprints with unestimated stories" - backlogs_sprint_unsized: "Project has stories on active or recently closed sprints that were not sized" - backlogs_sprints: "Sprints" backlogs_story: "Story" backlogs_story_type: "Story types" backlogs_task: "Task" backlogs_task_type: "Task type" - backlogs_velocity_missing: "No velocity could be calculated for this project" - backlogs_velocity_varies: "Velocity varies significantly over sprints" backlogs_wiki_template: "Template for sprint wiki page" - backlogs_empty_title: "No versions are defined to be used in backlogs" - backlogs_empty_action_text: "To get started with backlogs, create a new version and assign it to a backlogs column." - button_edit_wiki: "Edit wiki page" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "The following errors were encountered:" - error_intro_singular: "The following error was encountered:" - error_outro: "Please correct the above errors before submitting again." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ideal" - inclusion: "is not included in the list" - label_back_to_project: "Back to project page" - label_backlog: "Backlog" label_backlogs: "Backlogs" label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "IDs of blocked work packages" - label_burndown: "Burndown" label_column_in_backlog: "Column in backlog" - label_hours: "hours" - label_work_package_hierarchy: "Work package Hierarchy" - label_master_backlog: "Master Backlog" - label_not_prioritized: "not prioritized" - label_points: "points" label_points_burn_down: "Down" label_points_burn_up: "Up" - label_product_backlog: "product backlog" - label_select_all: "Select all" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "sprint backlog" - label_sprint_cards: "Export cards" label_sprint_impediments: "Sprint Impediments" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Velocity %{velocity}, based on %{sprints} sprints with an average %{days} days" - label_stories: "Stories" - label_stories_tasks: "Stories/Tasks" label_task_board: "Task board" - label_version_setting: "Versions" - label_version: 'Version' - label_webcal: "Webcal Feed" - label_wiki: "Wiki" permission_view_master_backlog: "View master backlog" permission_view_taskboards: "View taskboards" permission_select_done_statuses: "Select done statuses" permission_update_sprints: "Update sprints" - points_accepted: "points accepted" - points_committed: "points committed" - points_resolved: "points resolved" - points_to_accept: "points not accepted" - points_to_resolve: "points not resolved" project_module_backlogs: "Backlogs" - rb_label_copy_tasks: "Copy work packages" - rb_label_copy_tasks_all: "All" - rb_label_copy_tasks_none: "None" - rb_label_copy_tasks_open: "Open" - rb_label_link_to_original: "Include link to original story" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "remaining work" - required_burn_rate_hours: "required burn rate (hours)" - required_burn_rate_points: "required burn rate (points)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Column in backlog" version_settings_display_option_left: "left" version_settings_display_option_none: "none" diff --git a/modules/backlogs/config/locales/crowdin/be.yml b/modules/backlogs/config/locales/crowdin/be.yml index 77933695330..08248f9f22f 100644 --- a/modules/backlogs/config/locales/crowdin/be.yml +++ b/modules/backlogs/config/locales/crowdin/be.yml @@ -25,6 +25,8 @@ be: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Пазіцыя" story_points: "Story Points" @@ -43,128 +45,101 @@ be: attributes: task_type: "Task type" backlogs: - add_new_story: "Новая Гісторыя" any: "любы" - backlog_settings: "Налады бэклогу" - burndown_graph: "Burndown Graph" - card_paper_size: "Paper size for card printing" - chart_options: "Chart options" - close: "Зачыніць" - column_width: "Column width:" - date: "Day" + column_width: "Column width" definition_of_done: "Definition of Done" - generating_chart: "Generating Graph..." - hours: "Гадзіны" impediment: "Impediment" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." work_package_is_closed: "Work package is done, when" label_is_done_status: "Status %{status_name} means done" - no_burndown_data: "No burndown data available. It is necessary to have the sprint start- and end dates set." - points: "Кропкі" + points_label: + one: "point" + few: "points" + many: "points" + other: "points" positions_could_not_be_rebuilt: "Positions could not be rebuilt." positions_rebuilt_successfully: "Positions rebuilt successfully." - properties: "Properties" rebuild: "Перабудаваць" rebuild_positions: "Rebuild positions" remaining_hours: "Remaining work" - remaining_hours_ideal: "Remaining work (ideal)" show_burndown_chart: "Burndown Chart" story: "Гісторыя" - story_points: "Story Points" - story_points_ideal: "Story Points (ideal)" + story_points: + one: "%{count} story point" + few: "%{count} story points" + many: "%{count} story points" + other: "%{count} story points" task: "Task" task_color: "Task color" unassigned: "Unassigned" user_preference: header_backlogs: "Backlogs module" button_update_backlogs: "Update backlogs module" - x_more: "%{count} more..." - backlogs_active: "active" - backlogs_any: "любы" - backlogs_inactive: "Project shows no activity" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Points burn up/down" backlogs_product_backlog: "Product backlog" - backlogs_product_backlog_is_empty: "Product backlog is empty" - backlogs_product_backlog_unsized: "The top of the product backlog has unsized stories" - backlogs_sizing_inconsistent: "Story sizes vary against their estimates" - backlogs_sprint_notes_missing: "Closed sprints without retrospective/review notes" - backlogs_sprint_unestimated: "Closed or active sprints with unestimated stories" - backlogs_sprint_unsized: "Project has stories on active or recently closed sprints that were not sized" - backlogs_sprints: "Sprints" backlogs_story: "Гісторыя" backlogs_story_type: "Story types" backlogs_task: "Task" backlogs_task_type: "Task type" - backlogs_velocity_missing: "No velocity could be calculated for this project" - backlogs_velocity_varies: "Velocity varies significantly over sprints" backlogs_wiki_template: "Template for sprint wiki page" - backlogs_empty_title: "No versions are defined to be used in backlogs" - backlogs_empty_action_text: "To get started with backlogs, create a new version and assign it to a backlogs column." - button_edit_wiki: "Edit wiki page" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "The following errors were encountered:" - error_intro_singular: "The following error was encountered:" - error_outro: "Please correct the above errors before submitting again." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ideal" - inclusion: "is not included in the list" - label_back_to_project: "Back to project page" - label_backlog: "Backlog" label_backlogs: "Backlogs" label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "IDs of blocked work packages" - label_burndown: "Burndown" label_column_in_backlog: "Column in backlog" - label_hours: "hours" - label_work_package_hierarchy: "Work package Hierarchy" - label_master_backlog: "Master Backlog" - label_not_prioritized: "not prioritized" - label_points: "points" label_points_burn_down: "Down" label_points_burn_up: "Up" - label_product_backlog: "product backlog" - label_select_all: "Select all" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "sprint backlog" - label_sprint_cards: "Export cards" label_sprint_impediments: "Sprint Impediments" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Velocity %{velocity}, based on %{sprints} sprints with an average %{days} days" - label_stories: "Stories" - label_stories_tasks: "Stories/Tasks" label_task_board: "Task board" - label_version_setting: "Versions" - label_version: 'Version' - label_webcal: "Webcal Feed" - label_wiki: "Wiki" permission_view_master_backlog: "View master backlog" permission_view_taskboards: "View taskboards" permission_select_done_statuses: "Select done statuses" permission_update_sprints: "Update sprints" - points_accepted: "points accepted" - points_committed: "points committed" - points_resolved: "points resolved" - points_to_accept: "points not accepted" - points_to_resolve: "points not resolved" project_module_backlogs: "Backlogs" - rb_label_copy_tasks: "Copy work packages" - rb_label_copy_tasks_all: "All" - rb_label_copy_tasks_none: "None" - rb_label_copy_tasks_open: "Open" - rb_label_link_to_original: "Include link to original story" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "remaining work" - required_burn_rate_hours: "required burn rate (hours)" - required_burn_rate_points: "required burn rate (points)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Column in backlog" version_settings_display_option_left: "left" version_settings_display_option_none: "none" diff --git a/modules/backlogs/config/locales/crowdin/bg.yml b/modules/backlogs/config/locales/crowdin/bg.yml index e99dea07e05..74fa2864dbc 100644 --- a/modules/backlogs/config/locales/crowdin/bg.yml +++ b/modules/backlogs/config/locales/crowdin/bg.yml @@ -25,6 +25,8 @@ bg: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Position" story_points: "Точки на история" @@ -43,128 +45,97 @@ bg: attributes: task_type: "Task type" backlogs: - add_new_story: "Нова история" any: "всяко" - backlog_settings: "Настройки за закъснения" - burndown_graph: "Графика на изгаряне" - card_paper_size: "Размер на хартията за печат на карти" - chart_options: "Опции на диаграмата" - close: "Затворени" - column_width: "Ширина на колоните:" - date: "Ден" + column_width: "Column width" definition_of_done: "Definition of Done" - generating_chart: "Генериране на графика..." - hours: "Часа" impediment: "Препядствие" label_versions_default_fold_state: "Показване на сгънати версиите " caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." work_package_is_closed: "Работният пакет е готов, когато" label_is_done_status: "Статус %{status_name} означава готово" - no_burndown_data: "Няма налични данни за изгаряне. Необходимо е да имате зададени начална и крайна дата на спринта." - points: "Точки" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "Позициите не могат да бъдат възстановени." positions_rebuilt_successfully: "Позициите са възстановени успешно." - properties: "Свойства" rebuild: "Възстанови" rebuild_positions: "Възстановете позициите" remaining_hours: "Оставаща работа" - remaining_hours_ideal: "Оставащи часове (идеално)" show_burndown_chart: "Графика на изгаряне" story: "История" - story_points: "Точки на история" - story_points_ideal: "Точки на история (идеални)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "Задача" task_color: "Цвят на задачата" unassigned: "Неприсвоен" user_preference: header_backlogs: "Backlogs module" button_update_backlogs: "Update backlogs module" - x_more: "%{count} more..." - backlogs_active: "Активен" - backlogs_any: "всякакви" - backlogs_inactive: "Проектът не показва активност" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Точки на изгаряне нагоре/надолу" backlogs_product_backlog: "Натрупване на продукти" - backlogs_product_backlog_is_empty: "Натрупването на продукти е празно" - backlogs_product_backlog_unsized: "В горната част на изоставането на продукта има не оразмерени материали." - backlogs_sizing_inconsistent: "Размерите на историите се различават спрямо техните оценки" - backlogs_sprint_notes_missing: "Затворени спринтове без бележки за ретроспекция/преглед" - backlogs_sprint_unestimated: "Затворени или активни спринтове с неоценени истории" - backlogs_sprint_unsized: "Проектът има истории за активни или наскоро затворени спринтове, които не са оразмерени" - backlogs_sprints: "Спринтове" backlogs_story: "История" backlogs_story_type: "Типове истории" backlogs_task: "Задачата" backlogs_task_type: "Тип задача" - backlogs_velocity_missing: "Не може да се изчисли скорост за този проект" - backlogs_velocity_varies: "Скоростта варира значително при спринтове" backlogs_wiki_template: "Шаблон за уики страница на спринт" - backlogs_empty_title: "Не са дефинирани версии, които да се използват в неизпълнени задачи" - backlogs_empty_action_text: "За да започнете с неизпълнени задачи, създайте нова версия и я присвоете на колона с натрупани документи." - button_edit_wiki: "Редактиране на wiki страници" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "Бяха открити следните грешки:" - error_intro_singular: "Възникна следната грешка:" - error_outro: "Please correct the above errors before submitting again." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "идеален" - inclusion: "не е включен в списъка" - label_back_to_project: "Назад към страницата на проекта" - label_backlog: "Backlog" label_backlogs: "Backlogs" label_backlogs_unconfigured: "Все още не сте конфигурирали Неизпълнени задачи. Моля, отидете на %{administration} > %{plugins}, след което щракнете върху връзката %{configure} за този плъгин. След като зададете полетата, върнете се на тази страница, за да започнете да използвате инструмента." label_blocks_ids: "ID на блокирани работни пакети" - label_burndown: "Изгаряне" label_column_in_backlog: "Column in backlog" - label_hours: "часове" - label_work_package_hierarchy: "Йерархия на работните пакети" - label_master_backlog: "Главна неизпълнена работа" - label_not_prioritized: "без приоритет" - label_points: "Точки" label_points_burn_down: "Надолу" label_points_burn_up: "Нагоре" - label_product_backlog: "Натрупване на продукти" - label_select_all: "Избери всички" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "списък с неизпълнени работи в спринта" - label_sprint_cards: "Експорт карти" label_sprint_impediments: "Пречки за спринт" - label_sprint_name: "Спринт \"%{name}\"" - label_sprint_velocity: "Velocity %{velocity}, based on %{sprints} sprints with an average %{days} days" - label_stories: "Stories" - label_stories_tasks: "Stories/Tasks" label_task_board: "Task board" - label_version_setting: "Versions" - label_version: 'Версия' - label_webcal: "Webcal Feed" - label_wiki: "Wiki" permission_view_master_backlog: "View master backlog" permission_view_taskboards: "View taskboards" permission_select_done_statuses: "Изберете готови състояния" permission_update_sprints: "Update sprints" - points_accepted: "points accepted" - points_committed: "points committed" - points_resolved: "points resolved" - points_to_accept: "points not accepted" - points_to_resolve: "points not resolved" project_module_backlogs: "Назад" - rb_label_copy_tasks: "Copy work packages" - rb_label_copy_tasks_all: "All" - rb_label_copy_tasks_none: "None" - rb_label_copy_tasks_open: "Open" - rb_label_link_to_original: "Включете връзка към оригиналната история" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "Оставаща работа" - required_burn_rate_hours: "required burn rate (hours)" - required_burn_rate_points: "required burn rate (points)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Колона в баклога" version_settings_display_option_left: "left" version_settings_display_option_none: "none" diff --git a/modules/backlogs/config/locales/crowdin/ca.yml b/modules/backlogs/config/locales/crowdin/ca.yml index 24f6c4d556c..e97a3abc45a 100644 --- a/modules/backlogs/config/locales/crowdin/ca.yml +++ b/modules/backlogs/config/locales/crowdin/ca.yml @@ -25,6 +25,8 @@ ca: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Posició" story_points: "Punts d'història" @@ -43,128 +45,97 @@ ca: attributes: task_type: "Task type" backlogs: - add_new_story: "Nova història" any: "qualsevol" - backlog_settings: "Configuració dels backlogs" - burndown_graph: "Gràfic de progrés" - card_paper_size: "Mida de paper per a impressió de targetes" - chart_options: "Opcions del gràfic" - close: "Tanca" - column_width: "Amplada de la columna:" - date: "Dia" + column_width: "Column width" definition_of_done: "Definició de fet" - generating_chart: "Generant gràfic..." - hours: "Hores" impediment: "Impediment" label_versions_default_fold_state: "Mostra les versions contretes" caption_versions_default_fold_state: "Les versions no s'expandiran per defecte quan es visualitzin els registres enrere. Cada un ha d'ampliar-se manualment." work_package_is_closed: "Paquet de treball completat, quan" label_is_done_status: "L'estat %{status_name} significa fet" - no_burndown_data: "No hi ha dades de progrés disponibles. És necessari tenir establertes les dates de l'inici i el final del sprint." - points: "Punts" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "No es poden reconstruir les posicions." positions_rebuilt_successfully: "Posicions reconstruïdes amb èxit." - properties: "Propietats" rebuild: "Reconstruir" rebuild_positions: "Reconstruir posicions" remaining_hours: "Treball restant" - remaining_hours_ideal: "Treball restant (ideal)" show_burndown_chart: "Diagrama de progrés" story: "Història" - story_points: "Punts d'història" - story_points_ideal: "Punts d'història (ideal)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "Tasca" task_color: "Color de la tasca" unassigned: "No assignat" user_preference: header_backlogs: "Mòdul Backlogs" button_update_backlogs: "Actualitza el mòdul de registres enrere" - x_more: "%{count} més..." - backlogs_active: "actiu" - backlogs_any: "qualsevol" - backlogs_inactive: "El projecte no mostra cap activitat" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Punts de progrés positius/negatius" backlogs_product_backlog: "Llista de pendents del producte" - backlogs_product_backlog_is_empty: "La llista de pendents del producte està buida" - backlogs_product_backlog_unsized: "La part superior de la llista de pendents del producte ha històries no quantificades" - backlogs_sizing_inconsistent: "Les mides de les històries són diferents de les seves estimacions" - backlogs_sprint_notes_missing: "Sprints tancats sense retrospectiva/notes de revisió" - backlogs_sprint_unestimated: "Sprints tancades o actives amb històries sense estimar" - backlogs_sprint_unsized: "El projecte té històries actives o sprints tancats recentment que no estaven quantificats" - backlogs_sprints: "Sprints" backlogs_story: "Història" backlogs_story_type: "Tipus d'història" backlogs_task: "Tasca" backlogs_task_type: "Tipus de tasca" - backlogs_velocity_missing: "No es pot calcular la velocitat d'aquest projecte" - backlogs_velocity_varies: "La velocitat varia significativament d'un sprint a l'altre" backlogs_wiki_template: "Plantilla per a la pàgina wiki de l'sprint" - backlogs_empty_title: "No hi ha versions definides per a ser utilitzades als backlogs" - backlogs_empty_action_text: "Per a introduir-vos amb els backlogs, genereu una nova versió i assigneu-la a la columna backlogs." - button_edit_wiki: "Edita la pàgina wiki" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "S'han produït els següents errors:" - error_intro_singular: "S'ha produït el següent error:" - error_outro: "Corregiu els errors anteriors abans d'enviar-ho una altra vegada." - event_sprint_description: "%{summary}: %{url}%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ideal" - inclusion: "no s'ha inclòs a la llista" - label_back_to_project: "Tornar a la pàgina del projecte" - label_backlog: "Backlog" label_backlogs: "Backlogs" label_backlogs_unconfigured: "No has configurat les llistes de pendents encara. Si us plau, ves a %{administration} > %{plugins}, a continuació, fes clic a l'enllaç de %{configure} per a aquest plugin. Una vegada hagis omplert els camps, torna a aquesta pàgina per començar a utilitzar l'eina." label_blocks_ids: "Identificadors dels paquets de treball bloquejats" - label_burndown: "Progrés" label_column_in_backlog: "Columna al backlog" - label_hours: "hores" - label_work_package_hierarchy: "Jerarquia de paquet de treball" - label_master_backlog: "Backlog mestre" - label_not_prioritized: "no prioritzats" - label_points: "punts" label_points_burn_down: "A baix" label_points_burn_up: "Amunt" - label_product_backlog: "llista de pendents del producte" - label_select_all: "Selecciona-ho tot" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "sprint backlog" - label_sprint_cards: "Exporta les targetes" label_sprint_impediments: "Impediments de sprint" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Velocitat de %{velocity}, basada en %{sprints} sprints amb una mitjana de %{days} dies" - label_stories: "Històries" - label_stories_tasks: "Històries/Tasques" label_task_board: "Tauler de tasques" - label_version_setting: "Versions" - label_version: 'Versió' - label_webcal: "Webcal Feed" - label_wiki: "Wiki" permission_view_master_backlog: "Visualitza el backlog mestre" permission_view_taskboards: "Visualitza els taulers de tasques" permission_select_done_statuses: "Selecciona els estats acabats" permission_update_sprints: "Actualitza els sprints" - points_accepted: "punts acceptats" - points_committed: "punts comesos" - points_resolved: "punts resolts" - points_to_accept: "punts no acceptats" - points_to_resolve: "punts no resolts" project_module_backlogs: "Backlogs" - rb_label_copy_tasks: "Copia paquets de treball" - rb_label_copy_tasks_all: "Totes" - rb_label_copy_tasks_none: "Cap" - rb_label_copy_tasks_open: "Obre" - rb_label_link_to_original: "Incloure l'enllaç a la història original" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "treball restant" - required_burn_rate_hours: "ritme de progrés necessari (hores)" - required_burn_rate_points: "ritme de progrés necessari (punts)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Columna al backlog" version_settings_display_option_left: "esquerra" version_settings_display_option_none: "cap" diff --git a/modules/backlogs/config/locales/crowdin/ckb-IR.yml b/modules/backlogs/config/locales/crowdin/ckb-IR.yml index 18ad23bfd25..c813b5d1c2a 100644 --- a/modules/backlogs/config/locales/crowdin/ckb-IR.yml +++ b/modules/backlogs/config/locales/crowdin/ckb-IR.yml @@ -25,6 +25,8 @@ ckb-IR: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "پێگە" story_points: "Story Points" @@ -43,128 +45,97 @@ ckb-IR: attributes: task_type: "Task type" backlogs: - add_new_story: "New Story" any: "any" - backlog_settings: "Backlogs settings" - burndown_graph: "Burndown Graph" - card_paper_size: "Paper size for card printing" - chart_options: "Chart options" - close: "Close" - column_width: "Column width:" - date: "Day" + column_width: "Column width" definition_of_done: "Definition of Done" - generating_chart: "Generating Graph..." - hours: "Hours" impediment: "Impediment" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." work_package_is_closed: "Work package is done, when" label_is_done_status: "Status %{status_name} means done" - no_burndown_data: "No burndown data available. It is necessary to have the sprint start- and end dates set." - points: "Points" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "Positions could not be rebuilt." positions_rebuilt_successfully: "Positions rebuilt successfully." - properties: "Properties" rebuild: "Rebuild" rebuild_positions: "Rebuild positions" remaining_hours: "Remaining work" - remaining_hours_ideal: "Remaining work (ideal)" show_burndown_chart: "Burndown Chart" story: "Story" - story_points: "Story Points" - story_points_ideal: "Story Points (ideal)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "Task" task_color: "Task color" unassigned: "Unassigned" user_preference: header_backlogs: "Backlogs module" button_update_backlogs: "Update backlogs module" - x_more: "%{count} more..." - backlogs_active: "active" - backlogs_any: "any" - backlogs_inactive: "Project shows no activity" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Points burn up/down" backlogs_product_backlog: "Product backlog" - backlogs_product_backlog_is_empty: "Product backlog is empty" - backlogs_product_backlog_unsized: "The top of the product backlog has unsized stories" - backlogs_sizing_inconsistent: "Story sizes vary against their estimates" - backlogs_sprint_notes_missing: "Closed sprints without retrospective/review notes" - backlogs_sprint_unestimated: "Closed or active sprints with unestimated stories" - backlogs_sprint_unsized: "Project has stories on active or recently closed sprints that were not sized" - backlogs_sprints: "Sprints" backlogs_story: "Story" backlogs_story_type: "Story types" backlogs_task: "Task" backlogs_task_type: "Task type" - backlogs_velocity_missing: "No velocity could be calculated for this project" - backlogs_velocity_varies: "Velocity varies significantly over sprints" backlogs_wiki_template: "Template for sprint wiki page" - backlogs_empty_title: "No versions are defined to be used in backlogs" - backlogs_empty_action_text: "To get started with backlogs, create a new version and assign it to a backlogs column." - button_edit_wiki: "Edit wiki page" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "The following errors were encountered:" - error_intro_singular: "The following error was encountered:" - error_outro: "Please correct the above errors before submitting again." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ideal" - inclusion: "is not included in the list" - label_back_to_project: "Back to project page" - label_backlog: "Backlog" label_backlogs: "Backlogs" label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "IDs of blocked work packages" - label_burndown: "Burndown" label_column_in_backlog: "Column in backlog" - label_hours: "hours" - label_work_package_hierarchy: "Work package Hierarchy" - label_master_backlog: "Master Backlog" - label_not_prioritized: "not prioritized" - label_points: "points" label_points_burn_down: "Down" label_points_burn_up: "Up" - label_product_backlog: "product backlog" - label_select_all: "Select all" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "sprint backlog" - label_sprint_cards: "Export cards" label_sprint_impediments: "Sprint Impediments" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Velocity %{velocity}, based on %{sprints} sprints with an average %{days} days" - label_stories: "Stories" - label_stories_tasks: "Stories/Tasks" label_task_board: "Task board" - label_version_setting: "Versions" - label_version: 'Version' - label_webcal: "Webcal Feed" - label_wiki: "Wiki" permission_view_master_backlog: "View master backlog" permission_view_taskboards: "View taskboards" permission_select_done_statuses: "Select done statuses" permission_update_sprints: "Update sprints" - points_accepted: "points accepted" - points_committed: "points committed" - points_resolved: "points resolved" - points_to_accept: "points not accepted" - points_to_resolve: "points not resolved" project_module_backlogs: "Backlogs" - rb_label_copy_tasks: "Copy work packages" - rb_label_copy_tasks_all: "All" - rb_label_copy_tasks_none: "None" - rb_label_copy_tasks_open: "Open" - rb_label_link_to_original: "Include link to original story" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "remaining work" - required_burn_rate_hours: "required burn rate (hours)" - required_burn_rate_points: "required burn rate (points)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Column in backlog" version_settings_display_option_left: "left" version_settings_display_option_none: "none" diff --git a/modules/backlogs/config/locales/crowdin/cs.yml b/modules/backlogs/config/locales/crowdin/cs.yml index 0c6ccdd6ec9..689464be225 100644 --- a/modules/backlogs/config/locales/crowdin/cs.yml +++ b/modules/backlogs/config/locales/crowdin/cs.yml @@ -25,6 +25,8 @@ cs: description: "Tento modul přidává funkce umožňující agilním týmům pracovat s OpenProject v Scrum projektech." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Pozice" story_points: "Body příběhu" @@ -43,128 +45,101 @@ cs: attributes: task_type: "Typ úlohy" backlogs: - add_new_story: "Nový příběh" any: "jakákoliv" - backlog_settings: "Nastavení nevyřízených položek" - burndown_graph: "Graf vypálení" - card_paper_size: "Velikost papíru pro tisk karet" - chart_options: "Možnosti grafu" - close: "Zavřít" - column_width: "Šířka sloupce:" - date: "Den" + column_width: "Column width" definition_of_done: "Definice dokončena" - generating_chart: "Generování grafu..." - hours: "Hodiny" impediment: "Impediment" label_versions_default_fold_state: " Verze zobrazit srolovaně" caption_versions_default_fold_state: "Verze se při prohlížení nevyřízených žádostí ve výchozím nastavení nerozbalují. Každou z nich je třeba rozbalit ručně." work_package_is_closed: "Pracovní balíček je hotov, když" label_is_done_status: "Stav %{status_name} znamená hotovo" - no_burndown_data: "Nejsou k dispozici žádná data o vypálení. Je nutné mít nastavena data zahájení a ukončení sprintu." - points: "Body" + points_label: + one: "point" + few: "points" + many: "points" + other: "points" positions_could_not_be_rebuilt: "Pozice nelze znovu sestavit." positions_rebuilt_successfully: "Pozice úspěšně přestavěny." - properties: "Vlastnosti" rebuild: "Znovu vytvořit" rebuild_positions: "Znovu sestavit pozice" remaining_hours: "Zbývající práce" - remaining_hours_ideal: "Zbývající hodiny (ideální)" show_burndown_chart: "Spálovací graf" story: "Příběh" - story_points: "Body příběhu" - story_points_ideal: "Body příběhu (ideální)" + story_points: + one: "%{count} story point" + few: "%{count} story points" + many: "%{count} story points" + other: "%{count} story points" task: "Úkol" task_color: "Barva úlohy" unassigned: "Nepřiřazeno" user_preference: header_backlogs: "Backlogs module" button_update_backlogs: "Update backlogs module" - x_more: "%{count} další..." - backlogs_active: "aktivní" - backlogs_any: "jakákoliv" - backlogs_inactive: "Projekt nezobrazuje žádnou aktivitu" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Body popáleniny nahoru/dolů" backlogs_product_backlog: "Nevyřízené produkty" - backlogs_product_backlog_is_empty: "Nevyřízené položky produktu jsou prázdné" - backlogs_product_backlog_unsized: "Nejvyšší počet nevyřízených produktů má nevelké příběhy" - backlogs_sizing_inconsistent: "Velikost příběhu se liší podle jejich odhadů" - backlogs_sprint_notes_missing: "Uzavřený sprint bez retrospektivních/přezkoumávaných poznámek" - backlogs_sprint_unestimated: "Uzavřený nebo aktivní sprint s nespletitými příběhy" - backlogs_sprint_unsized: "Projekt má příběhy o aktivních nebo nedávno uzavřených sprintech, které neměly velikost" - backlogs_sprints: "Sprinty" backlogs_story: "Příběh" backlogs_story_type: "Typy článků" backlogs_task: "Úkol" backlogs_task_type: "Typ úkolu" - backlogs_velocity_missing: "Pro tento projekt nelze vypočítat rychlost" - backlogs_velocity_varies: "Rychlost se výrazně liší po sprintech" backlogs_wiki_template: "Šablona pro stránku se sprint wiki" - backlogs_empty_title: "Nejsou definovány žádné verze pro použití v neuzavřených záznamech" - backlogs_empty_action_text: "Chcete-li začít s nevyřízené položky, vytvořte novou verzi a přiřaďte ji do sloupce nevyřízené položky." - button_edit_wiki: "Upravit wiki stránku" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "Došlo k následujícím chybám:" - error_intro_singular: "Došlo k následující chybě:" - error_outro: "Před dalším odesláním opravte výše uvedené chyby." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ideální" - inclusion: "není zahrnuto v seznamu" - label_back_to_project: "Zpět na stránku projektu" - label_backlog: "Nevyřízené položky" label_backlogs: "Nevyřízené položky" label_backlogs_unconfigured: "Zatím nemáte nakonfigurované nevyřízené záznamy. Přejděte na %{administration} > %{plugins}a poté klikněte na odkaz %{configure} pro tento plugin. Jakmile nastavíte pole, vraťte se na tuto stránku a začněte používat nástroj." label_blocks_ids: "ID blokovaných pracovních balíčků" - label_burndown: "Spálení" label_column_in_backlog: "Sloupec v nevyřízené pozici" - label_hours: "hodiny" - label_work_package_hierarchy: "Hierarchie pracovního balíčku" - label_master_backlog: "Hlavní nevyřízené položky" - label_not_prioritized: "neupřednostňováno" - label_points: "body" label_points_burn_down: "Dolů" label_points_burn_up: "Nahoru" - label_product_backlog: "nevyřízené produkty" - label_select_all: "Vybrat vše" label_select_type: "Vyberte typ" label_select_types: "Vyberte typy" label_selected_type: "Vybraný typ" label_selected_types: "Vybrané typy" - label_sprint_backlog: "nevyřízené sprint" - label_sprint_cards: "Exportovat karty" label_sprint_impediments: "Běh impedimenty" - label_sprint_name: "Běh \"%{name}\"" - label_sprint_velocity: "Rychlost %{velocity}na základě %{sprints} běhů s průměrným %{days} dny" - label_stories: "Příběhy" - label_stories_tasks: "Příběhy/Úkoly" label_task_board: "Tabule úkolů" - label_version_setting: "Verze" - label_version: 'Verze' - label_webcal: "Webcal kanál" - label_wiki: "Wiki" permission_view_master_backlog: "Zobrazit hlavní nevyřízené položky" permission_view_taskboards: "Zobrazit tabuly úkolů" permission_select_done_statuses: "Vybrat stavy dokončených" permission_update_sprints: "Aktualizovat běhy" - points_accepted: "Přijaté body" - points_committed: "přislíbené body" - points_resolved: "body vyřešeny" - points_to_accept: "body nejsou přijaty" - points_to_resolve: "body nebyly vyřešeny" project_module_backlogs: "Nevyřízené položky" - rb_label_copy_tasks: "Kopírovat pracovní balíčky" - rb_label_copy_tasks_all: "Vše" - rb_label_copy_tasks_none: "Žádný" - rb_label_copy_tasks_open: "Otevřít" - rb_label_link_to_original: "Zahrnout odkaz na původní příběh" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "Zbývající práce" - required_burn_rate_hours: "požadovaná rychlost hoření (v hodinách)" - required_burn_rate_points: "požadovaná rychlost hoření (body)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Sloupec v nevyřízené pozici" version_settings_display_option_left: "vlevo" version_settings_display_option_none: "žádný" diff --git a/modules/backlogs/config/locales/crowdin/da.yml b/modules/backlogs/config/locales/crowdin/da.yml index 1abf0a38f62..5e15d9e3e05 100644 --- a/modules/backlogs/config/locales/crowdin/da.yml +++ b/modules/backlogs/config/locales/crowdin/da.yml @@ -25,6 +25,8 @@ da: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Position" story_points: "Historiepunkter" @@ -43,128 +45,97 @@ da: attributes: task_type: "Opgavetype" backlogs: - add_new_story: "Ny historie" any: "alle" - backlog_settings: "Backlog-indstillinger" - burndown_graph: "Burndown-graf" - card_paper_size: "Papirformat til kortudskrivning" - chart_options: "Diagrammuligheder" - close: "Luk" - column_width: "Kolonnebredde:" - date: "Dag" + column_width: "Column width" definition_of_done: "Definition af Udført" - generating_chart: "Genererer graf..." - hours: "Timer" impediment: "Hindring" label_versions_default_fold_state: "Vis versioner sammenfoldet" caption_versions_default_fold_state: "Versioner vil ikke blive udvidet som standard, når man ser på backlogs. Hver enkelt version skal udvides manuelt." work_package_is_closed: "Arbejdspakken er færdig, når" label_is_done_status: "Status %{status_name} betyder færdig" - no_burndown_data: "Ingen tilgængelige burndown-data. Sprint start- og slutdatoerne er obligatoriske." - points: "Point" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "Positioner kunne ikke genopbygges." positions_rebuilt_successfully: "Positioner er genopbygget." - properties: "Egenskaber" rebuild: "Genopbyg" rebuild_positions: "Genopbyg positioner" remaining_hours: "Resterende arbejde" - remaining_hours_ideal: "Resterende arbejde (ideelt)" show_burndown_chart: "Burndown-diagram" story: "Historie" - story_points: "Historiepunkter" - story_points_ideal: "Historiepunkter (ideelt)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "Opgave" task_color: "Opgavefarve" unassigned: "Utildelt" user_preference: header_backlogs: "Backlog-modul" button_update_backlogs: "Opdater backlog-modul" - x_more: "%{count} flere..." - backlogs_active: "aktiv" - backlogs_any: "alle" - backlogs_inactive: "Projekt viser ingen aktivitet" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Punkt burn op/ned" backlogs_product_backlog: "Produkt-backlog" - backlogs_product_backlog_is_empty: "Produkt-backlog er tom" - backlogs_product_backlog_unsized: "Toppen af produkt-backloggen har historier uden størrelser" - backlogs_sizing_inconsistent: "Historiestørrelser varierer i forhold til deres skøn" - backlogs_sprint_notes_missing: "Lukkede sprinter uden retrospektive/gennemgangsnotater" - backlogs_sprint_unestimated: "Lukkede eller aktive sprinter med uestimerede historier" - backlogs_sprint_unsized: "Projekt har historier om aktive eller nyligt lukkede sprints, som ikke var uden størrelse" - backlogs_sprints: "Sprints" backlogs_story: "Historie" backlogs_story_type: "Historietyper" backlogs_task: "Opgave" backlogs_task_type: "Opgavetype" - backlogs_velocity_missing: "Ingen hastighed kunne beregnes for dette projekt" - backlogs_velocity_varies: "Hastighed varierer betydeligt sprints imellem" backlogs_wiki_template: "Skabelon til sprint-wikiside" - backlogs_empty_title: "Ingen versioner defineret til brug i backlogs" - backlogs_empty_action_text: "For at komme i gang med backlogs, opret en ny version og tildel den til en backlogs-kolonne." - button_edit_wiki: "Redigér wikiside" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "kan ikke også være en historietype" - error_intro_plural: "Flg. fejl opstod:" - error_intro_singular: "Flg. fejl opstod:" - error_outro: "Ret ovenstående fejl før genindsendelse." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ideelt" - inclusion: "er ikke medtaget på listen" - label_back_to_project: "Tilbage til projektsiden" - label_backlog: "Backlog" label_backlogs: "Backlogs" label_backlogs_unconfigured: "Man har ikke opsat Backlogs endnu. Gå til %{administration} > %{plugins}, og klik dernæst på linket %{configure} til dette plugin. Når man har indstillet feltindhold, vend tilbage til denne side for at begynde at bruge værktøjet." label_blocks_ids: "ID'er for blokerede arbejdspakker" - label_burndown: "Burndown" label_column_in_backlog: "Kolonne i backlog" - label_hours: "timer" - label_work_package_hierarchy: "Arbejdspakkehierarki" - label_master_backlog: "Hoved-Backlog" - label_not_prioritized: "uprioriteret" - label_points: "punkter" label_points_burn_down: "Ned" label_points_burn_up: "Op" - label_product_backlog: "produkt-backlog" - label_select_all: "Vælg alle" label_select_type: "Vælg en type" label_select_types: "Vælg typer" label_selected_type: "Valgt type" label_selected_types: "Valgte typer" - label_sprint_backlog: "sprint-backlog" - label_sprint_cards: "Eksportere kort" label_sprint_impediments: "Sprint-hindringer" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Hastighed %{velocity}, baseret på %{sprints} sprints med en gennemsnitlig %{days} dage" - label_stories: "Historier" - label_stories_tasks: "Historier/Opgaver" label_task_board: "Opgaveoversigt" - label_version_setting: "Versioner" - label_version: 'Version' - label_webcal: "Webcal-feed" - label_wiki: "Wiki" permission_view_master_backlog: "Se hoved-backlog" permission_view_taskboards: "Vis opgaveoversigter" permission_select_done_statuses: "Vælg udført-statusser" permission_update_sprints: "Opdatere sprints" - points_accepted: "accepterede punkter" - points_committed: "committede punkter" - points_resolved: "løste punkter" - points_to_accept: "ikke-accepterede punkter" - points_to_resolve: "ikke-løste punkter" project_module_backlogs: "Backlogs" - rb_label_copy_tasks: "Kopiér arbejdspakker" - rb_label_copy_tasks_all: "Alle" - rb_label_copy_tasks_none: "Ingen" - rb_label_copy_tasks_open: "Åbn" - rb_label_link_to_original: "Medtag link til original historie" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "resterende arbejde" - required_burn_rate_hours: "krævet burn-hastighed (timer)" - required_burn_rate_points: "krævet burn-hastighed (point)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Kolonne i backlog" version_settings_display_option_left: "venstre" version_settings_display_option_none: "ingen" diff --git a/modules/backlogs/config/locales/crowdin/de.yml b/modules/backlogs/config/locales/crowdin/de.yml index a5fa4e959d2..77c4d219cfa 100644 --- a/modules/backlogs/config/locales/crowdin/de.yml +++ b/modules/backlogs/config/locales/crowdin/de.yml @@ -25,6 +25,8 @@ de: description: "Dieses Modul fügt Funktionen hinzu, die es agilen Teams ermöglichen, mit OpenProject in Scrum-Projekten zu arbeiten." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Position" story_points: "Story-Punkte" @@ -43,128 +45,97 @@ de: attributes: task_type: "Aufgaben-Typ" backlogs: - add_new_story: "Neue Aufgabe" any: "beliebig" - backlog_settings: "Backlog-Einstellungen" - burndown_graph: "Burndown Graph" - card_paper_size: "Format für Kartendruck" - chart_options: "Chart-Optionen" - close: "Schließen" - column_width: "Spaltenbreite:" - date: "Tag" + column_width: "Column width" definition_of_done: "Definition of Done" - generating_chart: "Generiere Graph..." - hours: "Stunden" impediment: "Hindernis" label_versions_default_fold_state: "Versionen eingeklappt anzeigen" caption_versions_default_fold_state: "Versionen werden beim Anzeigen des Backlogs standardmäßig nicht aufgeklappt. Sie müssen manuell geöffnet werden." work_package_is_closed: "Arbeitspaket ist abgeschlossen, wenn" label_is_done_status: "Status %{status_name} bedeutet abgeschlossen" - no_burndown_data: "Keine Burndown Graphen verfügbar. Start- und Enddaten der Sprints müssen definiert sein." - points: "Punkte" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "Positionen konnten nicht neu berechnet werden." positions_rebuilt_successfully: "Positionen wurden neu berechnet." - properties: "Eigenschaften" rebuild: "Neu berechnen" rebuild_positions: "Positionen neu berechnen" remaining_hours: "Verbleibender Aufwand" - remaining_hours_ideal: "Verbleibender Aufwand (ideal)" show_burndown_chart: "Burndown-Chart" story: "Story" - story_points: "Story Punkte" - story_points_ideal: "Story Punkte (ideal)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "Aufgabe" task_color: "Farbe für Aufgaben" unassigned: "Nicht zugewiesen" user_preference: header_backlogs: "Backlog-Modul" button_update_backlogs: "Backlog-Modul aktualisieren" - x_more: "%{count} mehr..." - backlogs_active: "aktiv" - backlogs_any: "beliebig" - backlogs_inactive: "Projekt zeigt keine Aktivität" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Burnup/-down Punkte" backlogs_product_backlog: "Produkt-Backlog" - backlogs_product_backlog_is_empty: "Produkt-Backlog ist leer" - backlogs_product_backlog_unsized: "Die Spitze des Produkt-Backlogs hat Stories ohne Aufwandsangaben" - backlogs_sizing_inconsistent: "Die Story-Aufwände weichen von den Schätzwerten ab" - backlogs_sprint_notes_missing: "Geschlossene Sprints ohne Closed sprints without Retrospective-/Besprechungsnotizen" - backlogs_sprint_unestimated: "Geschlossene oder aktive Sprints mit nicht abgeschätzten Stories" - backlogs_sprint_unsized: "Das Projekt hat Stories auf aktiven oder vor kurzem geschlossenen Sprints welche keine Aufwandsangaben enthalten" - backlogs_sprints: "Sprints" backlogs_story: "Story" backlogs_story_type: "Story-Typ" backlogs_task: "Aufgabe" backlogs_task_type: "Aufgaben-Typ" - backlogs_velocity_missing: "Es konnte keine Geschwindigkeit für dieses Projekt berechnet werden" - backlogs_velocity_varies: "Die Geschwindigkeit variiert stark zwischen den Sprints" backlogs_wiki_template: "Vorlage für das Sprint-Wiki" - backlogs_empty_title: "Für Backlogs sind keine Versionen definiert" - backlogs_empty_action_text: "Um mit Backlogs zu beginnen, erstellen Sie eine neue Version und fügen diese einer Backlogs-Spalte hinzu." - button_edit_wiki: "Wiki Seite bearbeiten" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "kann nicht auch ein Story-Typ sein" - error_intro_plural: "Die folgenden Fehler sind aufgetreten:" - error_intro_singular: "Der folgende Fehler ist aufgetreten:" - error_outro: "Bitte beheben Sie die obigen Fehler bevor Sie erneut abschicken." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "Ideal" - inclusion: "ist nicht in der Liste enthalten" - label_back_to_project: "Zurück zur Projektseite" - label_backlog: "Backlog" label_backlogs: "Backlogs" label_backlogs_unconfigured: "Sie haben noch keine Backlogs konfiguriert. Bitte gehen Sie auf %{administration} > %{plugins}, klicken Sie dann auf den %{configure} Link für dieses Plugin. Kommen Sie hierher zurück, sobald sie die Felder konfiguriert haben." label_blocks_ids: "IDs der blockierten Arbeitspakete" - label_burndown: "Burndown" label_column_in_backlog: "Spalte im Backlog" - label_hours: "Stunden" - label_work_package_hierarchy: "Arbeitspaketehierarchie" - label_master_backlog: "Master Backlog" - label_not_prioritized: "nicht priorisiert" - label_points: "Punkte" label_points_burn_down: "Runter" label_points_burn_up: "Hoch" - label_product_backlog: "Produkt Backlog" - label_select_all: "Alle auswählen" label_select_type: "Typ auswählen" label_select_types: "Typen auswählen" label_selected_type: "Ausgewählter Typ" label_selected_types: "Ausgewähle Typen" - label_sprint_backlog: "Sprint Backlog" - label_sprint_cards: "Karten exportieren" label_sprint_impediments: "Sprint Hindernisse" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Geschwindigkeit %{velocity}, basiert auf %{sprints} Sprints mit einem Durchschnitt von %{days} Tagen" - label_stories: "Stories" - label_stories_tasks: "Stories/Aufgaben" label_task_board: "Taskboard" - label_version_setting: "Versionen" - label_version: 'Version' - label_webcal: "Webcal Feed" - label_wiki: "Wiki" permission_view_master_backlog: "Master Backlog ansehen" permission_view_taskboards: "Taskboard ansehen" permission_select_done_statuses: "Abgeschlossene Status auswählen" permission_update_sprints: "Sprints bearbeiten" - points_accepted: "Akzeptierte Punkte" - points_committed: "Punkte abgeschlossen" - points_resolved: "Beschlossene Punkte" - points_to_accept: "Nicht akzeptierte Punkte" - points_to_resolve: "Nicht beschlossene Punkte" project_module_backlogs: "Backlogs" - rb_label_copy_tasks: "Arbeitspakete kopieren" - rb_label_copy_tasks_all: "Alle" - rb_label_copy_tasks_none: "Keine" - rb_label_copy_tasks_open: "Öffnen" - rb_label_link_to_original: "Link zur Original-Story einfügen" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "verbleibender Aufwand" - required_burn_rate_hours: "benötigte Burn-Rate (Stunden)" - required_burn_rate_points: "benötigte Burn-Rate (Punkte)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Spalte im Backlog" version_settings_display_option_left: "links" version_settings_display_option_none: "keine" diff --git a/modules/backlogs/config/locales/crowdin/el.yml b/modules/backlogs/config/locales/crowdin/el.yml index 9f9517ff4b0..ad11cf51a58 100644 --- a/modules/backlogs/config/locales/crowdin/el.yml +++ b/modules/backlogs/config/locales/crowdin/el.yml @@ -25,6 +25,8 @@ el: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Θέση" story_points: "Πόντοι Ιστορίας" @@ -43,128 +45,97 @@ el: attributes: task_type: "Task type" backlogs: - add_new_story: "Νέα Ιστορία" any: "οποιoδήποτε" - backlog_settings: "Ρυθμίσεις backlog" - burndown_graph: "Γράφημα Burndown" - card_paper_size: "Μέγεθος χαρτιού για εκτύπωση καρτών" - chart_options: "Επιλογές διαγράμματος" - close: "Κλείσιμο" - column_width: "Πλάτος στήλης:" - date: "Ημέρα" + column_width: "Column width" definition_of_done: "Ορισμός των Ολοκληρωμένων" - generating_chart: "Δημιουργία γραφήματος..." - hours: "Ώρες" impediment: "Εμπόδιο" label_versions_default_fold_state: "Εμφάνιση συμπτυγμένων εκδόσεων" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." work_package_is_closed: "Το πακέτο εργασίας ολοκληρώνεται, όταν" label_is_done_status: "Η κατάσταση %{status_name} σημαίνει ολοκληρωμένη" - no_burndown_data: "Δεν υπάρχουν διαθέσιμα δεδομένα burndown. Είναι απαραίτητο να έχετε ορίσει ημερομηνίες έναρξης και λήξης για το sprint." - points: "Πόντοι" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "Οι θέσεις δεν ήταν δυνατό να ανοικοδομηθούν." positions_rebuilt_successfully: "Οι θέσεις ξανακτίστηκαν επιτυχώς." - properties: "Ιδιότητες" rebuild: "Ανοικοδόμηση" rebuild_positions: "Ανοικοδόμηση θέσεων" remaining_hours: "Υπολειπόμενες Ώρες" - remaining_hours_ideal: "Υπολειπόμενες Ώρες (ιδανικά)" show_burndown_chart: "Διάγραμμα Burndown" story: "Ιστορία" - story_points: "Πόντοι Ιστορίας" - story_points_ideal: "Πόντοι Ιστορίας (ιδανικά)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "Εργασία" task_color: "Χρώμα εργασίας" unassigned: "Μη Αναθετημένο" user_preference: header_backlogs: "Backlogs module" button_update_backlogs: "Update backlogs module" - x_more: "%{count} ακόμα..." - backlogs_active: "ενεργό" - backlogs_any: "οποιoδήποτε" - backlogs_inactive: "Το έργο δεν φαίνεται να έχει δραστηριότητα" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Points burn up/down" backlogs_product_backlog: "Backlog προϊόντος" - backlogs_product_backlog_is_empty: "Το backlog του προϊόντος είναι άδειο" - backlogs_product_backlog_unsized: "Η κορυφή του backlog προϊόντων έχει μη εκτιμημένα μεγέθη ιστοριών" - backlogs_sizing_inconsistent: "Τα μεγέθη ιστοριών ποικίλλουν συγκριτικά με τις εκτιμήσεις τους" - backlogs_sprint_notes_missing: "Κλειστά sprints χωρίς αναδρομικές/αναθεωρημένες σημειώσεις" - backlogs_sprint_unestimated: "Κλειστά ή ενεργά sprints με μη εκτιμημένες ιστορίες" - backlogs_sprint_unsized: "Το έργο έχει ιστορίες σε sprints που είναι ενεργά ή που έκλεισαν πρόσφατα οι οποίες δεν έχουν εκτιμημένο μέγεθος" - backlogs_sprints: "Sprints" backlogs_story: "Ιστορία" backlogs_story_type: "Τύποι ιστορίας" backlogs_task: "Εργασία" backlogs_task_type: "Τύπος εργασίας" - backlogs_velocity_missing: "Δεν ήταν δυνατός ο υπολογισμός ταχύτητας για αυτό το έργο" - backlogs_velocity_varies: "Η ταχύτητα διαφέρει σημαντικά σε σχέση με τα sprints" backlogs_wiki_template: "Πρότυπο για τη σελίδα wiki του sprint" - backlogs_empty_title: "Δεν έχουν οριστεί εκδόσεις που μπορούν να χρησιμοποιηθούν σε backlogs" - backlogs_empty_action_text: "Για να ξεκινήσετε με τα backlogs, δημιουργήστε μια καινούργια έκδοση και αναθέστε την σε μια στήλη backlog." - button_edit_wiki: "Επεξεργασία σελίδας wiki" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "Παρουσιάστηκαν τα ακόλουθα σφάλματα:" - error_intro_singular: "Συναντήθηκε το ακόλουθο σφάλμα:" - error_outro: "Παρακαλούμε διορθώστε τα παραπάνω σφάλματα πριν το ξαναυποβάλλετε." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ιδανικό" - inclusion: "δεν συμπεριλαμβάνεται στη λίστα" - label_back_to_project: "Επιστροφή στη σελίδα έργου" - label_backlog: "Backlog" label_backlogs: "Backlogs" label_backlogs_unconfigured: "Δεν έχετε διαμορφώσει τα Backlogs ακόμη. Παρακαλούμε πηγαίνετε στο %{administration} > %{plugins}, έπειτα κάντε κλικ στον σύνδεσμο %{configure} για αυτό το πρόσθετο. Μόλις έχετε ορίσει τα πεδία, επιστρέψτε σε αυτή την σελίδα για να αρχίσετε να χρησιμοποιείτε το εργαλείο." label_blocks_ids: "Ταυτότητες μπλοκαρισμένων πακέτων εργασίας" - label_burndown: "Burndown" label_column_in_backlog: "Στήλη στο backlog" - label_hours: "ώρες" - label_work_package_hierarchy: "Ιεραρχία πακέτου εργασίας" - label_master_backlog: "Κύριο Backlog" - label_not_prioritized: "δεν έχει προτεραιότητα" - label_points: "πόντοι" label_points_burn_down: "Κάτω" label_points_burn_up: "Πάνω" - label_product_backlog: "backlog προϊόντος" - label_select_all: "Επιλογή όλων" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "sprint backlog" - label_sprint_cards: "Εξαγωγή καρτών" label_sprint_impediments: "Εμπόδια Sprint" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Ταχύτητα %{velocity}, με βάση τα %{sprints} sprints με μέσο όρο %{days} ημέρες" - label_stories: "Ιστορίες" - label_stories_tasks: "Ιστορίες/Εργασίες" label_task_board: "Πίνακας εργασιών" - label_version_setting: "Εκδόσεις" - label_version: 'Έκδοση' - label_webcal: "Τροφοδοσία Webcal" - label_wiki: "Wiki" permission_view_master_backlog: "Εμφάνιση του κύριου backlog" permission_view_taskboards: "Εμφάνιση πινάκων εργασίας" permission_select_done_statuses: "Select done statuses" permission_update_sprints: "Ενημέρωση των sprints" - points_accepted: "πόντοι που έγιναν αποδεκτοί" - points_committed: "πόντοι που δεσμεύτηκαν" - points_resolved: "πόντοι που επιλύθηκαν" - points_to_accept: "πόντοι που δεν έγιναν αποδεκτοί" - points_to_resolve: "πόντοι που δεν επιλύθηκαν" project_module_backlogs: "Backlogs" - rb_label_copy_tasks: "Αντιγραφή πακέτων εργασίας" - rb_label_copy_tasks_all: "Όλα" - rb_label_copy_tasks_none: "Κανένα" - rb_label_copy_tasks_open: "Ανοιχτό" - rb_label_link_to_original: "Συμπερίληψη συνδέσμου για την αρχική ιστορία" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "remaining work" - required_burn_rate_hours: "απαιτούμενο burn rate (ώρες)" - required_burn_rate_points: "απαιτούμενο burn rate (πόντοι)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Στήλη στο backlog" version_settings_display_option_left: "αριστερά" version_settings_display_option_none: "κανένα" diff --git a/modules/backlogs/config/locales/crowdin/eo.yml b/modules/backlogs/config/locales/crowdin/eo.yml index 89a7b1031e3..9127ed8b4ea 100644 --- a/modules/backlogs/config/locales/crowdin/eo.yml +++ b/modules/backlogs/config/locales/crowdin/eo.yml @@ -25,6 +25,8 @@ eo: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Posicio" story_points: "Historiaj poentoj" @@ -43,128 +45,97 @@ eo: attributes: task_type: "Task type" backlogs: - add_new_story: "Nova historio" any: "ajna" - backlog_settings: "Backlogs settings" - burndown_graph: "Burndown Graph" - card_paper_size: "Paper size for card printing" - chart_options: "Chart options" - close: "Fermi" - column_width: "Column width:" - date: "Tago" + column_width: "Column width" definition_of_done: "Definition of Done" - generating_chart: "Generating Graph..." - hours: "Horoj" impediment: "Impediment" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." work_package_is_closed: "Work package is done, when" label_is_done_status: "Status %{status_name} means done" - no_burndown_data: "No burndown data available. It is necessary to have the sprint start- and end dates set." - points: "Poentoj" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "Positions could not be rebuilt." positions_rebuilt_successfully: "Positions rebuilt successfully." - properties: "Properties" rebuild: "Rebuild" rebuild_positions: "Rebuild positions" remaining_hours: "Remaining work" - remaining_hours_ideal: "Remaining work (ideal)" show_burndown_chart: "Burndown Chart" story: "Historio" - story_points: "Historiaj poentoj" - story_points_ideal: "Historiaj poentoj (ideala)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "Tasko" task_color: "Task color" unassigned: "Unassigned" user_preference: header_backlogs: "Backlogs module" button_update_backlogs: "Update backlogs module" - x_more: "%{count} pli..." - backlogs_active: "aktiva" - backlogs_any: "ajna" - backlogs_inactive: "Project shows no activity" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Points burn up/down" backlogs_product_backlog: "Product backlog" - backlogs_product_backlog_is_empty: "Product backlog is empty" - backlogs_product_backlog_unsized: "The top of the product backlog has unsized stories" - backlogs_sizing_inconsistent: "Story sizes vary against their estimates" - backlogs_sprint_notes_missing: "Closed sprints without retrospective/review notes" - backlogs_sprint_unestimated: "Closed or active sprints with unestimated stories" - backlogs_sprint_unsized: "Project has stories on active or recently closed sprints that were not sized" - backlogs_sprints: "Sprints" backlogs_story: "Historio" backlogs_story_type: "Story types" backlogs_task: "Tasko" backlogs_task_type: "Task type" - backlogs_velocity_missing: "No velocity could be calculated for this project" - backlogs_velocity_varies: "Velocity varies significantly over sprints" backlogs_wiki_template: "Template for sprint wiki page" - backlogs_empty_title: "No versions are defined to be used in backlogs" - backlogs_empty_action_text: "To get started with backlogs, create a new version and assign it to a backlogs column." - button_edit_wiki: "Edit wiki page" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "The following errors were encountered:" - error_intro_singular: "The following error was encountered:" - error_outro: "Please correct the above errors before submitting again." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ideala" - inclusion: "is not included in the list" - label_back_to_project: "Back to project page" - label_backlog: "Backlog" label_backlogs: "Backlogs" label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "ID de baritaj laborpakaĵoj" - label_burndown: "Burndown" label_column_in_backlog: "Column in backlog" - label_hours: "horoj" - label_work_package_hierarchy: "Work package Hierarchy" - label_master_backlog: "Master Backlog" - label_not_prioritized: "not prioritized" - label_points: "poentoj" label_points_burn_down: "Malsupren" label_points_burn_up: "Supren" - label_product_backlog: "product backlog" - label_select_all: "Elekti ĉiujn" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "sprint backlog" - label_sprint_cards: "Export cards" label_sprint_impediments: "Sprint Impediments" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Velocity %{velocity}, based on %{sprints} sprints with an average %{days} days" - label_stories: "Stories" - label_stories_tasks: "Stories/Tasks" label_task_board: "Task board" - label_version_setting: "Versioj" - label_version: 'Versio' - label_webcal: "Webcal Feed" - label_wiki: "Vikio" permission_view_master_backlog: "View master backlog" permission_view_taskboards: "View taskboards" permission_select_done_statuses: "Select done statuses" permission_update_sprints: "Update sprints" - points_accepted: "points accepted" - points_committed: "points committed" - points_resolved: "points resolved" - points_to_accept: "points not accepted" - points_to_resolve: "points not resolved" project_module_backlogs: "Backlogs" - rb_label_copy_tasks: "Kopiu laborpakaĵojn" - rb_label_copy_tasks_all: "Ĉiuj" - rb_label_copy_tasks_none: "Neniu" - rb_label_copy_tasks_open: "Malfermi" - rb_label_link_to_original: "Include link to original story" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "remaining work" - required_burn_rate_hours: "required burn rate (hours)" - required_burn_rate_points: "required burn rate (points)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Column in backlog" version_settings_display_option_left: "maldektre" version_settings_display_option_none: "neniu" diff --git a/modules/backlogs/config/locales/crowdin/es.yml b/modules/backlogs/config/locales/crowdin/es.yml index 333deae7671..6c539d24af0 100644 --- a/modules/backlogs/config/locales/crowdin/es.yml +++ b/modules/backlogs/config/locales/crowdin/es.yml @@ -25,6 +25,8 @@ es: description: "Este módulo añade funciones que permiten a los equipos Agile trabajar con OpenProject en proyectos Scrum." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Posición" story_points: "Puntos de Historia" @@ -43,128 +45,97 @@ es: attributes: task_type: "Tipo de tarea" backlogs: - add_new_story: "Nueva historia" any: "cualquiera" - backlog_settings: "Configuración de backlogs" - burndown_graph: "Grafico de Trabajo Pendiente" - card_paper_size: "Tamaño de papel para la impresión de tarjetas" - chart_options: "Opciones de gráfico" - close: "Cerrar" - column_width: "Anchura de columna:" - date: "Día" + column_width: "Column width" definition_of_done: "Criterio de Aceptación" - generating_chart: "Generando Gráfico..." - hours: "Horas" impediment: "Impedimento" label_versions_default_fold_state: "Mostrar versiones colapsadas" caption_versions_default_fold_state: "Las versiones no se expandirán por defecto al visualizar los trabajos pendientes. Cada una deberá expandirse manualmente." work_package_is_closed: "El paquete de trabajo esta terminado, cuando" label_is_done_status: "El estado %{status_name} significa completado" - no_burndown_data: "No hay datos de trabajo pendiente disponisbles. Es necesario tener un sprint con las fechas de inicio y fin asignadas." - points: "Puntos" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "Las posiciones no pudieron ser reconstruidas." positions_rebuilt_successfully: "Posiciones reconstruidas con éxito." - properties: "Propiedades" rebuild: "Reconstruir" rebuild_positions: "Reconstruir posiciones" remaining_hours: "Trabajo restante" - remaining_hours_ideal: "Trabajo restante (ideal)" show_burndown_chart: "Diagrama de Quemado" story: "Historia" - story_points: "Puntos de Historia" - story_points_ideal: "Puntos de la historia (ideal)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "Tarea" task_color: "Color de la tarea" unassigned: "No asignado" user_preference: header_backlogs: "Módulo de trabajos pendientes" button_update_backlogs: "Actualizar módulo de trabajos pendientes" - x_more: "%{count} más..." - backlogs_active: "activo" - backlogs_any: "cualquiera" - backlogs_inactive: "El proyecto no muestra ninguna actividad" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Puntos de quemado arriba/abajo" backlogs_product_backlog: "Cartera de producto" - backlogs_product_backlog_is_empty: "La cartera de productos está vacía" - backlogs_product_backlog_unsized: "El top del backlog del producto tiene historias no cuantificadas" - backlogs_sizing_inconsistent: "Los tamaños de las historias varían respecto a sus estimaciones" - backlogs_sprint_notes_missing: "Iteraciones cerradas sin notas/revisiones restrospectivas" - backlogs_sprint_unestimated: "Iteraciones activas o cerradas con historias sin calcular" - backlogs_sprint_unsized: "El proyecto tiene historias activas o iteraciones recientemente terminadas que no han sido medidas" - backlogs_sprints: "Iteraciones" backlogs_story: "Historia" backlogs_story_type: "Tipos de historia" backlogs_task: "Tarea" backlogs_task_type: "Tipo de tarea" - backlogs_velocity_missing: "No se puede calcular la velocidad de este proyecto" - backlogs_velocity_varies: "La velocidad varía significativamente a lo largo de las iteraciones" backlogs_wiki_template: "Plantilla para la página wiki de iteración" - backlogs_empty_title: "No se especificó ninguna versión en los backlogs" - backlogs_empty_action_text: "Para empezar con los trabajos pendientes, cree una nueva versión y asígnela a la columna de backlogs." - button_edit_wiki: "Editar página wiki" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "no puede ser también un tipo de historia" - error_intro_plural: "Se encontraron los siguientes errores:" - error_intro_singular: "Se ha encontrado el siguiente error:" - error_outro: "Por favor, corrija los errores anteriores antes de volver a enviarlo." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ideal" - inclusion: "no está incluido en la lista" - label_back_to_project: "Volver a la página de proyecto" - label_backlog: "Backlog" label_backlogs: "Backlogs" label_backlogs_unconfigured: "Todavía no ha configurado backlogs. Por favor, visite %{administration} > %{plugins}, luego haga clic en el enlace de %{configure} para esta extensión. Cuando haya establecido los campos, vuelva a esta página para empezar a usar la herramienta." label_blocks_ids: "ID de los paquetes de trabajo bloqueados" - label_burndown: "Burndown" label_column_in_backlog: "Columna en backlog" - label_hours: "horas" - label_work_package_hierarchy: "Jerarquía de paquete de trabajo" - label_master_backlog: "Backlog maestro" - label_not_prioritized: "no priorizado" - label_points: "puntos" label_points_burn_down: "Abajo" label_points_burn_up: "Arriba" - label_product_backlog: "backlog de producto" - label_select_all: "Seleccionar todos" label_select_type: "Selecciona un tipo" label_select_types: "Selecciona tipos" label_selected_type: "Tipo seleccionado" label_selected_types: "Tipos seleccionados" - label_sprint_backlog: "backlog de sprint" - label_sprint_cards: "Exportar tarjetas" label_sprint_impediments: "Impedimentos de sprint" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Velocidad %{velocity}, basada en %{sprints} sprints con una media de %{days} días" - label_stories: "Historias" - label_stories_tasks: "Historias/tareas" label_task_board: "Tablero de tareas" - label_version_setting: "Versiones" - label_version: 'Versión' - label_webcal: "Webcal Feed" - label_wiki: "Wiki" permission_view_master_backlog: "Ver backlog maestro" permission_view_taskboards: "Ver tablero de tareas" permission_select_done_statuses: "Seleccionar estados de finalización" permission_update_sprints: "Actualizar sprints" - points_accepted: "puntos aceptados" - points_committed: "puntos comprometidos" - points_resolved: "puntos resueltos" - points_to_accept: "puntos no aceptados" - points_to_resolve: "puntos no resueltos" project_module_backlogs: "Backlogs" - rb_label_copy_tasks: "Copiar paquetes de trabajo" - rb_label_copy_tasks_all: "Todo" - rb_label_copy_tasks_none: "Ninguno" - rb_label_copy_tasks_open: "Abrir" - rb_label_link_to_original: "Incluir enlace a la historia original" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "trabajo restante" - required_burn_rate_hours: "tasa de gasto necesaria (horas)" - required_burn_rate_points: "tasa de gasto necesaria (puntos)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Columna en backlog" version_settings_display_option_left: "izquierda" version_settings_display_option_none: "ninguno" diff --git a/modules/backlogs/config/locales/crowdin/et.yml b/modules/backlogs/config/locales/crowdin/et.yml index 57fb910afa7..e69f28c1bc2 100644 --- a/modules/backlogs/config/locales/crowdin/et.yml +++ b/modules/backlogs/config/locales/crowdin/et.yml @@ -25,6 +25,8 @@ et: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Asukoht" story_points: "Story Points" @@ -43,128 +45,97 @@ et: attributes: task_type: "Task type" backlogs: - add_new_story: "Uus lugu" any: "any" - backlog_settings: "Backlogs settings" - burndown_graph: "Burndown Graph" - card_paper_size: "Paper size for card printing" - chart_options: "Diagrammi valikud" - close: "Sulge" - column_width: "Veeru laius:" - date: "Päev" + column_width: "Column width" definition_of_done: "Definition of Done" - generating_chart: "Diagrammi loomine..." - hours: "Tunnid" impediment: "Impediment" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." work_package_is_closed: "Work package is done, when" label_is_done_status: "Status %{status_name} means done" - no_burndown_data: "No burndown data available. It is necessary to have the sprint start- and end dates set." - points: "Points" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "Positions could not be rebuilt." positions_rebuilt_successfully: "Positions rebuilt successfully." - properties: "Omadused" rebuild: "Taasta" rebuild_positions: "Rebuild positions" remaining_hours: "Remaining work" - remaining_hours_ideal: "Remaining work (ideal)" show_burndown_chart: "Burndown Chart" story: "Story" - story_points: "Story Points" - story_points_ideal: "Story Points (ideal)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "Ülesanne" task_color: "Ülesande värv" unassigned: "Määramata" user_preference: header_backlogs: "Backlogs module" button_update_backlogs: "Update backlogs module" - x_more: "veel %{count} ..." - backlogs_active: "aktiivne" - backlogs_any: "any" - backlogs_inactive: "Project shows no activity" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Points burn up/down" backlogs_product_backlog: "Product backlog" - backlogs_product_backlog_is_empty: "Product backlog is empty" - backlogs_product_backlog_unsized: "The top of the product backlog has unsized stories" - backlogs_sizing_inconsistent: "Story sizes vary against their estimates" - backlogs_sprint_notes_missing: "Closed sprints without retrospective/review notes" - backlogs_sprint_unestimated: "Closed or active sprints with unestimated stories" - backlogs_sprint_unsized: "Project has stories on active or recently closed sprints that were not sized" - backlogs_sprints: "Sprints" backlogs_story: "Story" backlogs_story_type: "Story types" backlogs_task: "Ülesanne" backlogs_task_type: "Task type" - backlogs_velocity_missing: "No velocity could be calculated for this project" - backlogs_velocity_varies: "Velocity varies significantly over sprints" backlogs_wiki_template: "Template for sprint wiki page" - backlogs_empty_title: "No versions are defined to be used in backlogs" - backlogs_empty_action_text: "To get started with backlogs, create a new version and assign it to a backlogs column." - button_edit_wiki: "Muuda vikilehte" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "The following errors were encountered:" - error_intro_singular: "The following error was encountered:" - error_outro: "Please correct the above errors before submitting again." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ideal" - inclusion: "ei ole nimistus" - label_back_to_project: "Tagasi projekti lehele" - label_backlog: "Backlog" label_backlogs: "Backlogs" label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "IDs of blocked work packages" - label_burndown: "Burndown" label_column_in_backlog: "Column in backlog" - label_hours: "tundi" - label_work_package_hierarchy: "Work package Hierarchy" - label_master_backlog: "Master Backlog" - label_not_prioritized: "not prioritized" - label_points: "punkti" label_points_burn_down: "Alla" label_points_burn_up: "Üles" - label_product_backlog: "product backlog" - label_select_all: "Vali kõik" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "sprint backlog" - label_sprint_cards: "Export cards" label_sprint_impediments: "Sprint Impediments" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Velocity %{velocity}, based on %{sprints} sprints with an average %{days} days" - label_stories: "Stories" - label_stories_tasks: "Stories/Tasks" label_task_board: "Task board" - label_version_setting: "Versioonid" - label_version: 'Versioon' - label_webcal: "Webcal Feed" - label_wiki: "Viki" permission_view_master_backlog: "View master backlog" permission_view_taskboards: "View taskboards" permission_select_done_statuses: "Vali valmis staatused" permission_update_sprints: "Update sprints" - points_accepted: "points accepted" - points_committed: "points committed" - points_resolved: "points resolved" - points_to_accept: "points not accepted" - points_to_resolve: "points not resolved" project_module_backlogs: "Backlogs" - rb_label_copy_tasks: "Copy work packages" - rb_label_copy_tasks_all: "Kõik" - rb_label_copy_tasks_none: "Pole" - rb_label_copy_tasks_open: "Avatud" - rb_label_link_to_original: "Include link to original story" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "remaining work" - required_burn_rate_hours: "required burn rate (hours)" - required_burn_rate_points: "required burn rate (points)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Column in backlog" version_settings_display_option_left: "vasakule" version_settings_display_option_none: "pole" diff --git a/modules/backlogs/config/locales/crowdin/eu.yml b/modules/backlogs/config/locales/crowdin/eu.yml index 99be029157e..9b3fb81a5b4 100644 --- a/modules/backlogs/config/locales/crowdin/eu.yml +++ b/modules/backlogs/config/locales/crowdin/eu.yml @@ -25,6 +25,8 @@ eu: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Kokapena" story_points: "Story Points" @@ -43,128 +45,97 @@ eu: attributes: task_type: "Task type" backlogs: - add_new_story: "New Story" any: "edozein" - backlog_settings: "Backlogs settings" - burndown_graph: "Burndown Graph" - card_paper_size: "Paper size for card printing" - chart_options: "Chart options" - close: "Itxi" - column_width: "Column width:" - date: "Eguna" + column_width: "Column width" definition_of_done: "Definition of Done" - generating_chart: "Grafikoa sortzen..." - hours: "Orduak" impediment: "Impediment" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." work_package_is_closed: "Work package is done, when" label_is_done_status: "Status %{status_name} means done" - no_burndown_data: "No burndown data available. It is necessary to have the sprint start- and end dates set." - points: "Puntuak" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "Positions could not be rebuilt." positions_rebuilt_successfully: "Positions rebuilt successfully." - properties: "Ezaugarriak" rebuild: "Rebuild" rebuild_positions: "Rebuild positions" remaining_hours: "Remaining work" - remaining_hours_ideal: "Remaining work (ideal)" show_burndown_chart: "Burndown Chart" story: "Story" - story_points: "Story Points" - story_points_ideal: "Story Points (ideal)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "Zeregina" task_color: "Zereginaren kolorea" unassigned: "Esleitu gabea" user_preference: header_backlogs: "Backlogs module" button_update_backlogs: "Update backlogs module" - x_more: "%{count} more..." - backlogs_active: "aktibo" - backlogs_any: "edozein" - backlogs_inactive: "Proiektuan ez da mugimendurik ikusten" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Points burn up/down" backlogs_product_backlog: "Product backlog" - backlogs_product_backlog_is_empty: "Product backlog is empty" - backlogs_product_backlog_unsized: "The top of the product backlog has unsized stories" - backlogs_sizing_inconsistent: "Story sizes vary against their estimates" - backlogs_sprint_notes_missing: "Closed sprints without retrospective/review notes" - backlogs_sprint_unestimated: "Closed or active sprints with unestimated stories" - backlogs_sprint_unsized: "Project has stories on active or recently closed sprints that were not sized" - backlogs_sprints: "Sprints" backlogs_story: "Story" backlogs_story_type: "Story types" backlogs_task: "Zeregina" backlogs_task_type: "Zeregin mota" - backlogs_velocity_missing: "Proiektu honetan ezin da abiadurarik kalkulatu." - backlogs_velocity_varies: "Velocity varies significantly over sprints" backlogs_wiki_template: "Template for sprint wiki page" - backlogs_empty_title: "No versions are defined to be used in backlogs" - backlogs_empty_action_text: "To get started with backlogs, create a new version and assign it to a backlogs column." - button_edit_wiki: "Edit wiki page" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "The following errors were encountered:" - error_intro_singular: "Hurrengo akatsa topatu da:" - error_outro: "Please correct the above errors before submitting again." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ideal" - inclusion: "ez dago zerrendan jasota" - label_back_to_project: "Proiektuaren orrira itzuli" - label_backlog: "Backlog" label_backlogs: "Backlogs" label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "IDs of blocked work packages" - label_burndown: "Burndown" label_column_in_backlog: "Column in backlog" - label_hours: "orduak" - label_work_package_hierarchy: "Work package Hierarchy" - label_master_backlog: "Master Backlog" - label_not_prioritized: "not prioritized" - label_points: "puntuak" label_points_burn_down: "Behera" label_points_burn_up: "Gora" - label_product_backlog: "product backlog" - label_select_all: "Hautatu guztiak" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "sprint backlog" - label_sprint_cards: "Export cards" label_sprint_impediments: "Sprint Impediments" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Velocity %{velocity}, based on %{sprints} sprints with an average %{days} days" - label_stories: "Stories" - label_stories_tasks: "Stories/Tasks" label_task_board: "Task board" - label_version_setting: "Bertsioak" - label_version: 'Bertsioa' - label_webcal: "Webcal Feed" - label_wiki: "Wikia" permission_view_master_backlog: "View master backlog" permission_view_taskboards: "View taskboards" permission_select_done_statuses: "Select done statuses" permission_update_sprints: "Update sprints" - points_accepted: "points accepted" - points_committed: "points committed" - points_resolved: "points resolved" - points_to_accept: "points not accepted" - points_to_resolve: "points not resolved" project_module_backlogs: "Backlogs" - rb_label_copy_tasks: "Copy work packages" - rb_label_copy_tasks_all: "Denak" - rb_label_copy_tasks_none: "Bat ere ez" - rb_label_copy_tasks_open: "Ireki" - rb_label_link_to_original: "Include link to original story" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "remaining work" - required_burn_rate_hours: "required burn rate (hours)" - required_burn_rate_points: "required burn rate (points)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Column in backlog" version_settings_display_option_left: "left" version_settings_display_option_none: "none" diff --git a/modules/backlogs/config/locales/crowdin/fa.yml b/modules/backlogs/config/locales/crowdin/fa.yml index cd5db0afbc8..c32841adeeb 100644 --- a/modules/backlogs/config/locales/crowdin/fa.yml +++ b/modules/backlogs/config/locales/crowdin/fa.yml @@ -25,6 +25,8 @@ fa: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Position" story_points: "Story Points" @@ -43,128 +45,97 @@ fa: attributes: task_type: "Task type" backlogs: - add_new_story: "New Story" any: "any" - backlog_settings: "ساماندهی کارهای انباشته" - burndown_graph: "Burndown Graph" - card_paper_size: "Paper size for card printing" - chart_options: "گزینه‌های نمودار\n \n" - close: "Close" - column_width: "Column width:" - date: "Day" + column_width: "Column width" definition_of_done: "Definition of Done" - generating_chart: "Generating Graph..." - hours: "Hours" impediment: "Impediment" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "در هنگام مشاهده backlog ها، نسخه‌ها به صورت پیش‌فرض بازنخواهندشد. هر کدام باید به صورت دستی باز شوند." work_package_is_closed: "Work package is done, when" label_is_done_status: "Status %{status_name} means done" - no_burndown_data: "No burndown data available. It is necessary to have the sprint start- and end dates set." - points: "Points" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "Positions could not be rebuilt." positions_rebuilt_successfully: "Positions rebuilt successfully." - properties: "Properties" rebuild: "Rebuild" rebuild_positions: "Rebuild positions" remaining_hours: "کار باقی‌مانده" - remaining_hours_ideal: "کار باقی مانده (ایده آل)" show_burndown_chart: "Burndown Chart" story: "Story" - story_points: "Story Points" - story_points_ideal: "Story Points (ideal)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "Task" task_color: "Task color" unassigned: "Unassigned" user_preference: header_backlogs: "ماژول backlogها" button_update_backlogs: "به روز رسانی ماژول backlog ها" - x_more: "%{count} more..." - backlogs_active: "active" - backlogs_any: "هرکدام" - backlogs_inactive: "Project shows no activity" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Points burn up/down" backlogs_product_backlog: "Product backlog" - backlogs_product_backlog_is_empty: "Product backlog is empty" - backlogs_product_backlog_unsized: "The top of the product backlog has unsized stories" - backlogs_sizing_inconsistent: "Story sizes vary against their estimates" - backlogs_sprint_notes_missing: "Closed sprints without retrospective/review notes" - backlogs_sprint_unestimated: "Closed or active sprints with unestimated stories" - backlogs_sprint_unsized: "پروژه کارهایی مربوط به دوره های کوتاه کاری فعال و تازه مسدود شده دارد که اندازه گیری نشده است" - backlogs_sprints: "دوره کوتاه کاری" backlogs_story: "Story" backlogs_story_type: "انواع کار" backlogs_task: "وظیفه" backlogs_task_type: "نوع وظیفه" - backlogs_velocity_missing: "هیچ شتابی برای این پروژه قابل محاسبه نبود" - backlogs_velocity_varies: "شتاب به طور قابل ملاحظه‌ای در تاخت‌ها تفاوت دارد" backlogs_wiki_template: "الگو برای صفحه دانشنامه تاخت" - backlogs_empty_title: "نسحه‌ای برای استفاده در پس‌افت‌ها تعریف نشده است" - backlogs_empty_action_text: "برای شروع کار با پس‌افت‌ها، یک نسخه‌ی جدید ایجاد کنید و به یکی از ستون‌های پس‌افت اختصاص دهید." - button_edit_wiki: "تدوین صفحه‌ی دانشنامه" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "خطاهای ذیل پیدا شدند:" - error_intro_singular: "خطای ذیل پیدا شد:" - error_outro: "لطفاً خطاهای بالا را قبل از تایید مجدد اصلاح کنید" - event_sprint_description: "%{summary}: %{url} %{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "مطلوب" - inclusion: "در لیست موجود نیست" - label_back_to_project: "برگشت به صفحه پروژه" - label_backlog: "پس‌افت" label_backlogs: "پس‌افت" label_backlogs_unconfigured: "شما هنوز پس‌افت‌ها را پیکربندی نکرده‌اید. لطفاً به %{administration} > %{plugins} بروید، سپس برای این افزونه روی %{configure} کلیک کنید. وقتی همۀ قسمت‌ها را تکمیل کردید، برای استفاده از این ابزار به همین صفحه برگردید. " label_blocks_ids: "شناسه‌های مسدود کاربسته‌ها" - label_burndown: "پایین‌سوز" label_column_in_backlog: "ستون در پس‌افت" - label_hours: "ساعت" - label_work_package_hierarchy: "رده‌بندی کاربسته" - label_master_backlog: "پس‌افت اصلی" - label_not_prioritized: "اولویت‌بندی نشده" - label_points: "امتیاز ها" label_points_burn_down: "پایین" label_points_burn_up: "بالا" - label_product_backlog: "پس‌افت محصول" - label_select_all: "انتخاب همه موارد" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "پس‌افت تاخت" - label_sprint_cards: "صدور کارت" label_sprint_impediments: " موانع تاخت" - label_sprint_name: "انتخاب \"%{name}\"" - label_sprint_velocity: "شتاب %{velocity}، بر اساس تاخت‌هایی %{sprints} با میانگین %{days} روز" - label_stories: "کارها" - label_stories_tasks: "کارها/وظیفه‌ها" label_task_board: "تابلوی وظیفه" - label_version_setting: "نسخه ها" - label_version: 'نسخه' - label_webcal: "فراخوانی منابع وب" - label_wiki: "دانشنامه" permission_view_master_backlog: "نمایش بک لاگ اصلی" permission_view_taskboards: "مشاهده تابلو وظایف" permission_select_done_statuses: "انتخاب وضعیت های انجام شده" permission_update_sprints: "بروزرسانی دوره‌ها" - points_accepted: "امتیازات پذیرفته شده" - points_committed: "امتیازات ثبت شده" - points_resolved: "امتیازات حل شده" - points_to_accept: "امتیازات رد شده" - points_to_resolve: "امتیازات حل نشده" project_module_backlogs: "وظایف" - rb_label_copy_tasks: "نسخه‌برداری بسته‌های کاری" - rb_label_copy_tasks_all: "همه" - rb_label_copy_tasks_none: "هیچ‌کدام" - rb_label_copy_tasks_open: "باز" - rb_label_link_to_original: "پیوند داخلی به داستان اصلی" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "کار باقیمانده" - required_burn_rate_hours: "میزان هزینه کرد لازم (ساعت)" - required_burn_rate_points: "میزان هزینه کرد لازم (امتیاز)" - todo_work_package_description: "%{summary}: %{url} %{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "ستون در پس‌افت" version_settings_display_option_left: "چپ" version_settings_display_option_none: "هیچ کدام" diff --git a/modules/backlogs/config/locales/crowdin/fi.yml b/modules/backlogs/config/locales/crowdin/fi.yml index a7118f35b99..b83f006523e 100644 --- a/modules/backlogs/config/locales/crowdin/fi.yml +++ b/modules/backlogs/config/locales/crowdin/fi.yml @@ -25,6 +25,8 @@ fi: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Sijainti" story_points: "Tarinapisteet" @@ -43,128 +45,97 @@ fi: attributes: task_type: "Task type" backlogs: - add_new_story: "Uusi tarina" any: "kaikki" - backlog_settings: "Työjonon asetukset" - burndown_graph: "Edistymiskäyrä" - card_paper_size: "Paperin koko kortille" - chart_options: "Kaavion asetukset" - close: "Sulje" - column_width: "Sarakeleveys:" - date: "Päivä" + column_width: "Column width" definition_of_done: "Valmiin määritelmä" - generating_chart: "Luodaan kaavio..." - hours: "Tunnit" impediment: "Este" label_versions_default_fold_state: "Näytä versiot ryhmiteltyinä" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." work_package_is_closed: "Tehtävä on valmis, kun" label_is_done_status: "Tila %{status_name} tarkoittaa valmista" - no_burndown_data: "Edistymistietoja ei ole saatavilla. Sprintin aloitus- ja lopetuspäivien asettaminen on välttämätöntä." - points: "Pisteet" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "Paikkoja ei voitu luoda uudelleen." positions_rebuilt_successfully: "Paikkojen uudelleenluonti onnistui." - properties: "Ominaisuudet" rebuild: "Luo uudelleen" rebuild_positions: "Luo paikat uudellee" remaining_hours: "Remaining work" - remaining_hours_ideal: "Remaining work (ideal)" show_burndown_chart: "Edistymiskuvaaja" story: "Tarina" - story_points: "Tarinapisteet" - story_points_ideal: "Tarinapisteet (ihanteellinen)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "Tehtävä" task_color: "Tehtävän väri" unassigned: "Määrittämätön" user_preference: header_backlogs: "Backlogs module" button_update_backlogs: "Update backlogs module" - x_more: "%{count} lisää..." - backlogs_active: "aktiivinen" - backlogs_any: "kaikki" - backlogs_inactive: "Projektissa ei ole toimintaa" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Pisteiden poltto ylös/alas" backlogs_product_backlog: "Tuotteen työjono" - backlogs_product_backlog_is_empty: "Tuotteen kehitysjo on tyhjä" - backlogs_product_backlog_unsized: "Tuotteen työjonossa on suunnittelemattomia tarinoita" - backlogs_sizing_inconsistent: "Tarinoiden koot vaihtelevat arvioistaan" - backlogs_sprint_notes_missing: "Suljetut sprintit joissa ei ole katselmointikommentteja" - backlogs_sprint_unestimated: "Suljetut tai avoimet sprintit joissa on suunnittelemattomia tarinoita" - backlogs_sprint_unsized: "Projektissa on aktiivisia tarinoita tai hiljattain suljettuja sprinttejä ei ole kokoluokitettu" - backlogs_sprints: "Sprintit" backlogs_story: "Tarina" backlogs_story_type: "Tarinatyypit" backlogs_task: "Tehtävä" backlogs_task_type: "Tehtävätyyppi" - backlogs_velocity_missing: "Tälle projektille ei voitu laskea nopeutta" - backlogs_velocity_varies: "Nopeus vaihtelee huomattavasti sprinttien välillä" backlogs_wiki_template: "Mallipohja sprintin wikisivusta" - backlogs_empty_title: "No versions are defined to be used in backlogs" - backlogs_empty_action_text: "To get started with backlogs, create a new version and assign it to a backlogs column." - button_edit_wiki: "Muokkaa wiki-sivua" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "Seuraavat virheet ilmenivät:" - error_intro_singular: "Ilmeni seuraava virhe:" - error_outro: "Korjaa edellä mainitut virheet ennen uudelleenlähettämistä." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ihanteellinen" - inclusion: "ei sisälly luetteloon" - label_back_to_project: "Takaisin projektisivulle" - label_backlog: "Työjono" label_backlogs: "Työjonot" label_backlogs_unconfigured: "Et ole määrittänyt vielä työjonoja. Siirry menuun %{administration} > %{plugins}, sitten klikkaa %{configure} linkkiä tälle liitännäiselle. Kun olet määrittänyt kentät, tule takaisin tälle sivulle aloittaaksesi työkalun käytön." label_blocks_ids: "Estettyjen työpakettien tunnukset" - label_burndown: "Edistyminen" label_column_in_backlog: "Sarake työjonossa" - label_hours: "tuntia" - label_work_package_hierarchy: "Tehtävähierarkia" - label_master_backlog: "Pääasiallinen työjono" - label_not_prioritized: "ei priorisoitu" - label_points: "pisteet" label_points_burn_down: "Alas" label_points_burn_up: "Ylös" - label_product_backlog: "tuotteen työjono" - label_select_all: "Valitse kaikki" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "sprintin työjono" - label_sprint_cards: "Vie kortteja" label_sprint_impediments: "Sprintin esteet" - label_sprint_name: "Sprintti \"%{name}\"" - label_sprint_velocity: "Nopeus %{velocity}, perustuu %{sprints} sprintteihin joissa keskimäärin %{days} päivää" - label_stories: "Tarinat" - label_stories_tasks: "Tarinat/Tehtävät" label_task_board: "Tehtävätaulu" - label_version_setting: "Versiot" - label_version: 'Versio' - label_webcal: "Webcal syöte" - label_wiki: "Wiki" permission_view_master_backlog: "Näytä pääasiallinen työjono" permission_view_taskboards: "Näytä työtaulut" permission_select_done_statuses: "Select done statuses" permission_update_sprints: "Päivitä sprinttejä" - points_accepted: "hyväksytyt pisteet" - points_committed: "pisteet joihin on sitouduttu" - points_resolved: "hyväksytyt pisteet" - points_to_accept: "hylätyt pisteet" - points_to_resolve: "ratkaisemattomat pisteet" project_module_backlogs: "Työjonot" - rb_label_copy_tasks: "Kopioi tehtäviä" - rb_label_copy_tasks_all: "Kaikki" - rb_label_copy_tasks_none: "Ei mitään" - rb_label_copy_tasks_open: "Avoin" - rb_label_link_to_original: "Sisällytä linkki alkuperäiseen tarinaan" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "remaining work" - required_burn_rate_hours: "tarvittava eistymisnopeus (tuntia)" - required_burn_rate_points: "tarvittava eistymisnopeus (pistettä)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Sarake työjonossa" version_settings_display_option_left: "vasen" version_settings_display_option_none: "ei mitään" diff --git a/modules/backlogs/config/locales/crowdin/fil.yml b/modules/backlogs/config/locales/crowdin/fil.yml index 1c62a26ab35..17d12463822 100644 --- a/modules/backlogs/config/locales/crowdin/fil.yml +++ b/modules/backlogs/config/locales/crowdin/fil.yml @@ -25,6 +25,8 @@ fil: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Posisyon" story_points: "Story Points" @@ -43,128 +45,97 @@ fil: attributes: task_type: "Task type" backlogs: - add_new_story: "New Story" any: "any" - backlog_settings: "Backlogs settings" - burndown_graph: "Burndown Graph" - card_paper_size: "Paper size for card printing" - chart_options: "Chart options" - close: "Isara" - column_width: "Column width:" - date: "Day" + column_width: "Column width" definition_of_done: "Definition of Done" - generating_chart: "Generating Graph..." - hours: "Mga oras" impediment: "Impediment" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." work_package_is_closed: "Work package is done, when" label_is_done_status: "Status %{status_name} means done" - no_burndown_data: "No burndown data available. It is necessary to have the sprint start- and end dates set." - points: "Points" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "Positions could not be rebuilt." positions_rebuilt_successfully: "Positions rebuilt successfully." - properties: "Mga property" rebuild: "Rebuild" rebuild_positions: "Rebuild positions" remaining_hours: "Remaining work" - remaining_hours_ideal: "Remaining work (ideal)" show_burndown_chart: "Burndown Chart" story: "Story" - story_points: "Story Points" - story_points_ideal: "Story Points (ideal)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "Gawain" task_color: "Task color" unassigned: "Unassigned" user_preference: header_backlogs: "Backlogs module" button_update_backlogs: "Update backlogs module" - x_more: "%{count} more..." - backlogs_active: "aktibo" - backlogs_any: "any" - backlogs_inactive: "Project shows no activity" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Points burn up/down" backlogs_product_backlog: "Product backlog" - backlogs_product_backlog_is_empty: "Product backlog is empty" - backlogs_product_backlog_unsized: "The top of the product backlog has unsized stories" - backlogs_sizing_inconsistent: "Story sizes vary against their estimates" - backlogs_sprint_notes_missing: "Closed sprints without retrospective/review notes" - backlogs_sprint_unestimated: "Closed or active sprints with unestimated stories" - backlogs_sprint_unsized: "Project has stories on active or recently closed sprints that were not sized" - backlogs_sprints: "Sprints" backlogs_story: "Story" backlogs_story_type: "Story types" backlogs_task: "Gawain" backlogs_task_type: "Task type" - backlogs_velocity_missing: "No velocity could be calculated for this project" - backlogs_velocity_varies: "Velocity varies significantly over sprints" backlogs_wiki_template: "Template for sprint wiki page" - backlogs_empty_title: "No versions are defined to be used in backlogs" - backlogs_empty_action_text: "To get started with backlogs, create a new version and assign it to a backlogs column." - button_edit_wiki: "Edit wiki page" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "The following errors were encountered:" - error_intro_singular: "The following error was encountered:" - error_outro: "Please correct the above errors before submitting again." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ideal" - inclusion: "is not included in the list" - label_back_to_project: "Back to project page" - label_backlog: "Backlog" label_backlogs: "Backlogs" label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "Mga ID ng naka-block na mga pakete sa gumagawa" - label_burndown: "Burndown" label_column_in_backlog: "Column in backlog" - label_hours: "mga oras" - label_work_package_hierarchy: "Work package Hierarchy" - label_master_backlog: "Master Backlog" - label_not_prioritized: "not prioritized" - label_points: "points" label_points_burn_down: "Down" label_points_burn_up: "Up" - label_product_backlog: "product backlog" - label_select_all: "Select all" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "sprint backlog" - label_sprint_cards: "Export cards" label_sprint_impediments: "Sprint Impediments" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Velocity %{velocity}, based on %{sprints} sprints with an average %{days} days" - label_stories: "Stories" - label_stories_tasks: "Stories/Tasks" label_task_board: "Task board" - label_version_setting: "Mga bersyon" - label_version: 'Bersyon' - label_webcal: "Webcal Feed" - label_wiki: "Wiki" permission_view_master_backlog: "View master backlog" permission_view_taskboards: "View taskboards" permission_select_done_statuses: "Select done statuses" permission_update_sprints: "Update sprints" - points_accepted: "points accepted" - points_committed: "points committed" - points_resolved: "points resolved" - points_to_accept: "points not accepted" - points_to_resolve: "points not resolved" project_module_backlogs: "Backlogs" - rb_label_copy_tasks: "Copy work packages" - rb_label_copy_tasks_all: "All" - rb_label_copy_tasks_none: "Wala" - rb_label_copy_tasks_open: "Bukas" - rb_label_link_to_original: "Include link to original story" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "remaining work" - required_burn_rate_hours: "required burn rate (hours)" - required_burn_rate_points: "required burn rate (points)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Column in backlog" version_settings_display_option_left: "left" version_settings_display_option_none: "wala" diff --git a/modules/backlogs/config/locales/crowdin/fr.yml b/modules/backlogs/config/locales/crowdin/fr.yml index b5ba3cf711d..8eaf0406c5b 100644 --- a/modules/backlogs/config/locales/crowdin/fr.yml +++ b/modules/backlogs/config/locales/crowdin/fr.yml @@ -25,6 +25,8 @@ fr: description: "Ce module ajoute des fonctionnalités permettant aux équipes agiles de travailler avec OpenProject dans le cadre de projets Scrum." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Position" story_points: "Points d'histoire" @@ -43,128 +45,97 @@ fr: attributes: task_type: "Type de tâche" backlogs: - add_new_story: "Nouvelle histoire" any: "tout" - backlog_settings: "Paramètres du backlog" - burndown_graph: "Graphique d'avancement" - card_paper_size: "Format de papier pour l'impression des cartes" - chart_options: "Options du graphique" - close: "Clôturer" - column_width: "Largeur de la colonne :" - date: "Jour" + column_width: "Column width" definition_of_done: "Définition de Fait" - generating_chart: "Génération du graphe…" - hours: "Heures" impediment: "Obstacle" label_versions_default_fold_state: "Afficher les versions de manière repliée" caption_versions_default_fold_state: "Les versions ne seront pas développées par défaut lors de l'affichage des backlogs. Chacune devra être développée manuellement." work_package_is_closed: "Le lot de travaux est fait lorsque" label_is_done_status: "Le statut %{status_name} signifie fait" - no_burndown_data: "Aucune donnée d'avancement disponible. Il est nécessaire que les dates de début et de fin du sprint soient définies." - points: "Points" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "Les positions n'ont pu être reconstruites." positions_rebuilt_successfully: "Positions reconstruites avec succès." - properties: "Propriétés" rebuild: "Reconstruire" rebuild_positions: "Reconstruire les positions" remaining_hours: "Travail restant" - remaining_hours_ideal: "Travail restant (idéal)" show_burndown_chart: "Graphique d'avancement" story: "Histoire" - story_points: "Points d'histoire" - story_points_ideal: "Points d'histoire (idéal)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "Tâche" task_color: "Couleur des tâches" unassigned: "Non assigné" user_preference: header_backlogs: "Module des backlogs" button_update_backlogs: "Mettre à jour le module des backlogs" - x_more: "%{count} de plus..." - backlogs_active: "actif" - backlogs_any: "tout" - backlogs_inactive: "Le projet ne montre aucune activité" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Les points évoluent vers le haut/bas" backlogs_product_backlog: "Backlog de produit" - backlogs_product_backlog_is_empty: "Le backlog de produit est vide" - backlogs_product_backlog_unsized: "Le dessus du backlog de produit contient des histoires non dimensionnées" - backlogs_sizing_inconsistent: "La taille des histoires varie à l'encontre des estimations" - backlogs_sprint_notes_missing: "Sprints fermés sans notes de rétrospective/revue" - backlogs_sprint_unestimated: "Sprints clôturés ou actifs avec des histoires non estimées" - backlogs_sprint_unsized: "Le projet contient des histoires sur des sprints actifs ou récemment clôturés dont la taille n'a pas été spécifiée" - backlogs_sprints: "Sprints" backlogs_story: "Histoire" backlogs_story_type: "Types d'histoire" backlogs_task: "Tâche" backlogs_task_type: "Type de tâche" - backlogs_velocity_missing: "Aucune vélocité n'a pu être calculée pour ce projet" - backlogs_velocity_varies: "La vélocité varie significativement d'un sprint à l'autre" backlogs_wiki_template: "Modèle pour page wiki de sprint" - backlogs_empty_title: "Aucune version n'est définie pour être utilisée dans les backlogs" - backlogs_empty_action_text: "Pour démarrer avec les backlogs, créez une nouvelle version et assignez-y une colonne de backlogs." - button_edit_wiki: "Éditer la page du wiki" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "ne peut pas être également un type d'histoire" - error_intro_plural: "Les erreurs suivantes ont été rencontrées :" - error_intro_singular: "L'erreur suivante a été rencontrée :" - error_outro: "Veuillez corriger les erreurs ci-dessus avant de soumettre à nouveau." - event_sprint_description: "%{summary} : %{url}\n%{description}" - event_sprint_summary: "%{project} : %{summary}" - ideal: "idéal" - inclusion: "n'est pas inclus(e) dans la liste" - label_back_to_project: "Retour à la page du projet" - label_backlog: "Backlog" label_backlogs: "Backlogs" label_backlogs_unconfigured: "Vous n'avez pas encore configuré Backlogs. Veuillez vous rendre dans %{administration} > %{plugins}, puis cliquer sur le lien %{configure} pour ce plugin. Une fois que vous avez défini les champs, revenez sur cette page pour commencer à utiliser l'outil." label_blocks_ids: "ID des lots de travaux bloqués" - label_burndown: "Avancement" label_column_in_backlog: "Colonne dans le backlog" - label_hours: "heures" - label_work_package_hierarchy: "Hiérarchie du lot de travaux" - label_master_backlog: "Backlog principal" - label_not_prioritized: "non priorisé" - label_points: "points" label_points_burn_down: "Vers le bas" label_points_burn_up: "Vers le haut" - label_product_backlog: "carnet de produit" - label_select_all: "Tout sélectionner" label_select_type: "Sélectionnez un type" label_select_types: "Sélectionnez le type" label_selected_type: "Type sélectionné" label_selected_types: "Types sélectionnés" - label_sprint_backlog: "carnet de sprint" - label_sprint_cards: "Exporter les cartes" label_sprint_impediments: "Obstacles de sprint" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Vélocité %{velocity}, issue de %{sprints} sprints avec une moyenne de %{days} jours" - label_stories: "Histoires" - label_stories_tasks: "Histoires/tâches" label_task_board: "Tableau des tâches" - label_version_setting: "Versions" - label_version: 'Version' - label_webcal: "Flux Webcal" - label_wiki: "Wiki" permission_view_master_backlog: "Afficher le backlog principal" permission_view_taskboards: "Voir les tableaux des tâches" permission_select_done_statuses: "Sélectionner les statuts terminés" permission_update_sprints: "Éditer les sprints" - points_accepted: "points acceptés" - points_committed: "points soumis" - points_resolved: "points résolus" - points_to_accept: "points non acceptés" - points_to_resolve: "points non résolus" project_module_backlogs: "Backlogs" - rb_label_copy_tasks: "Copier lots de travaux" - rb_label_copy_tasks_all: "Toutes" - rb_label_copy_tasks_none: "Aucune" - rb_label_copy_tasks_open: "Ouvertes" - rb_label_link_to_original: "Inclure le lien vers l'histoire originale" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "travail restant" - required_burn_rate_hours: "rythme nécessaire (heures)" - required_burn_rate_points: "rythme nécessaire (points)" - todo_work_package_description: "%{summary} : %{url}\n%{description}" - todo_work_package_summary: "%{type} : %{summary}" version_settings_display_label: "Colonne dans le backlog" version_settings_display_option_left: "gauche" version_settings_display_option_none: "aucune" diff --git a/modules/backlogs/config/locales/crowdin/he.yml b/modules/backlogs/config/locales/crowdin/he.yml index d54b49fcac9..7b8c93d2b69 100644 --- a/modules/backlogs/config/locales/crowdin/he.yml +++ b/modules/backlogs/config/locales/crowdin/he.yml @@ -25,6 +25,8 @@ he: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "מיקום" story_points: "Story Points" @@ -43,128 +45,101 @@ he: attributes: task_type: "Task type" backlogs: - add_new_story: "New Story" any: "הכל" - backlog_settings: "Backlogs settings" - burndown_graph: "Burndown Graph" - card_paper_size: "Paper size for card printing" - chart_options: "אפשרויות תרשים" - close: "סגור" - column_width: "רוחב עמודות" - date: "ימים" + column_width: "Column width" definition_of_done: "Definition of Done" - generating_chart: "Generating Graph..." - hours: "שעות" impediment: "Impediment" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." work_package_is_closed: "Work package is done, when" label_is_done_status: "Status %{status_name} means done" - no_burndown_data: "No burndown data available. It is necessary to have the sprint start- and end dates set." - points: "נקודות" + points_label: + one: "point" + two: "points" + many: "points" + other: "points" positions_could_not_be_rebuilt: "Positions could not be rebuilt." positions_rebuilt_successfully: "Positions rebuilt successfully." - properties: "Properties" rebuild: "Rebuild" rebuild_positions: "Rebuild positions" remaining_hours: "Remaining work" - remaining_hours_ideal: "Remaining work (ideal)" show_burndown_chart: "Burndown Chart" story: "סטורי" - story_points: "Story Points" - story_points_ideal: "Story Points (ideal)" + story_points: + one: "%{count} story point" + two: "%{count} story points" + many: "%{count} story points" + other: "%{count} story points" task: "משימה" task_color: "צבע משימה" unassigned: "לא מוקצה" user_preference: header_backlogs: "Backlogs module" button_update_backlogs: "Update backlogs module" - x_more: "%{count} more..." - backlogs_active: "פעיל" - backlogs_any: "הכל" - backlogs_inactive: "Project shows no activity" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Points burn up/down" backlogs_product_backlog: "Product backlog" - backlogs_product_backlog_is_empty: "צבר המוצרים ריק" - backlogs_product_backlog_unsized: "The top of the product backlog has unsized stories" - backlogs_sizing_inconsistent: "Story sizes vary against their estimates" - backlogs_sprint_notes_missing: "Closed sprints without retrospective/review notes" - backlogs_sprint_unestimated: "Closed or active sprints with unestimated stories" - backlogs_sprint_unsized: "לפרוייקט יש סיפורים על ספרינטים פעילים או שנסגרו לאחרונה שלא היו בגודל" - backlogs_sprints: "Sprints" backlogs_story: "סטורי" backlogs_story_type: "Story types" backlogs_task: "משימה" backlogs_task_type: "סוג משימה" - backlogs_velocity_missing: "No velocity could be calculated for this project" - backlogs_velocity_varies: "Velocity varies significantly over sprints" backlogs_wiki_template: "Template for sprint wiki page" - backlogs_empty_title: "No versions are defined to be used in backlogs" - backlogs_empty_action_text: "To get started with backlogs, create a new version and assign it to a backlogs column." - button_edit_wiki: "Edit wiki page" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "The following errors were encountered:" - error_intro_singular: "The following error was encountered:" - error_outro: "Please correct the above errors before submitting again." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ideal" - inclusion: "אינו נכלל ברשימה" - label_back_to_project: "Back to project page" - label_backlog: "Backlog" label_backlogs: "Backlogs" label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "המזהים של חבילות עבודה חסומים" - label_burndown: "Burndown" label_column_in_backlog: "Column in backlog" - label_hours: "שעות" - label_work_package_hierarchy: "Work package Hierarchy" - label_master_backlog: "Master Backlog" - label_not_prioritized: "not prioritized" - label_points: "points" label_points_burn_down: "Down" label_points_burn_up: "Up" - label_product_backlog: "product backlog" - label_select_all: "Select all" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "sprint backlog" - label_sprint_cards: "Export cards" label_sprint_impediments: "Sprint Impediments" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Velocity %{velocity}, based on %{sprints} sprints with an average %{days} days" - label_stories: "Stories" - label_stories_tasks: "Stories/Tasks" label_task_board: "Task board" - label_version_setting: "גירסאות" - label_version: 'גירסה' - label_webcal: "Webcal Feed" - label_wiki: "ויקי" permission_view_master_backlog: "View master backlog" permission_view_taskboards: "View taskboards" permission_select_done_statuses: "Select done statuses" permission_update_sprints: "Update sprints" - points_accepted: "points accepted" - points_committed: "points committed" - points_resolved: "points resolved" - points_to_accept: "points not accepted" - points_to_resolve: "points not resolved" project_module_backlogs: "Backlogs" - rb_label_copy_tasks: "Copy work packages" - rb_label_copy_tasks_all: "All" - rb_label_copy_tasks_none: "None" - rb_label_copy_tasks_open: "פתח" - rb_label_link_to_original: "Include link to original story" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "remaining work" - required_burn_rate_hours: "required burn rate (hours)" - required_burn_rate_points: "required burn rate (points)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Column in backlog" version_settings_display_option_left: "left" version_settings_display_option_none: "none" diff --git a/modules/backlogs/config/locales/crowdin/hi.yml b/modules/backlogs/config/locales/crowdin/hi.yml index 0dbd3af93db..70166309007 100644 --- a/modules/backlogs/config/locales/crowdin/hi.yml +++ b/modules/backlogs/config/locales/crowdin/hi.yml @@ -25,6 +25,8 @@ hi: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "स्थिति" story_points: "कहानी अंक" @@ -43,128 +45,97 @@ hi: attributes: task_type: "Task type" backlogs: - add_new_story: "नई कहानी" any: "कोई" - backlog_settings: "बैकलॉग सेटिंग्स" - burndown_graph: "बर्नडाउन ग्राफ" - card_paper_size: "कार्ड छपाई के लिए कागज का आकार" - chart_options: "चार्ट विकल्प" - close: "बंद करें" - column_width: "स्तंभ की चौड़ाई:" - date: "दिन" + column_width: "Column width" definition_of_done: "पूर्ण की परिभाषा" - generating_chart: "ग्राफ़ जनरेट कर रहा है..." - hours: "घंटे" impediment: "बाधा" label_versions_default_fold_state: "मुड़े हुए संस्करण दिखाएं" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." work_package_is_closed: "काम का पैकेज हो गया, जब" label_is_done_status: "स्थिति %{status_name} का अर्थ हो गया" - no_burndown_data: "जलने का कोई डेटा उपलब्ध नहीं है। स्प्रिंट की शुरुआत और समाप्ति तिथि निर्धारित करना आवश्यक है।" - points: "अंक" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "पदों का पुनर्निर्माण नहीं किया जा सका।" positions_rebuilt_successfully: "पदों का सफलतापूर्वक पुनर्निर्माण किया गया।" - properties: "गुण" rebuild: "फिर से बनाना" rebuild_positions: "पदों का पुनर्निर्माण करें" remaining_hours: "Remaining work" - remaining_hours_ideal: "Remaining work (ideal)" show_burndown_chart: "कार्य समय चार्ट" story: "कहानी" - story_points: "कहानी अंक" - story_points_ideal: "कहानी अंक (आदर्श)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "कार्य" task_color: "कार्य का रंग" unassigned: "सौंपे नहीं गए" user_preference: header_backlogs: "Backlogs module" button_update_backlogs: "Update backlogs module" - x_more: "%{count}और..." - backlogs_active: "सक्रिय" - backlogs_any: "कोई" - backlogs_inactive: "परियोजना गतिविधि नहीं दिखा रही है।" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Points burn up/down" backlogs_product_backlog: "Product backlog" - backlogs_product_backlog_is_empty: "Product backlog is empty" - backlogs_product_backlog_unsized: "The top of the product backlog has unsized stories" - backlogs_sizing_inconsistent: "Story sizes vary against their estimates" - backlogs_sprint_notes_missing: "Closed sprints without retrospective/review notes" - backlogs_sprint_unestimated: "Closed or active sprints with unestimated stories" - backlogs_sprint_unsized: "Project has stories on active or recently closed sprints that were not sized" - backlogs_sprints: "Sprints" backlogs_story: "कहानी" backlogs_story_type: "Story types" backlogs_task: "कार्य" backlogs_task_type: "कार्य प्रकार" - backlogs_velocity_missing: "No velocity could be calculated for this project" - backlogs_velocity_varies: "Velocity varies significantly over sprints" backlogs_wiki_template: "Template for sprint wiki page" - backlogs_empty_title: "No versions are defined to be used in backlogs" - backlogs_empty_action_text: "To get started with backlogs, create a new version and assign it to a backlogs column." - button_edit_wiki: "Edit wiki page" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "The following errors were encountered:" - error_intro_singular: "The following error was encountered:" - error_outro: "Please correct the above errors before submitting again." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ideal" - inclusion: "is not included in the list" - label_back_to_project: "Back to project page" - label_backlog: "Backlog" label_backlogs: "Backlogs" label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "IDs of blocked work packages" - label_burndown: "Burndown" label_column_in_backlog: "Column in backlog" - label_hours: "hours" - label_work_package_hierarchy: "Work package Hierarchy" - label_master_backlog: "Master Backlog" - label_not_prioritized: "not prioritized" - label_points: "points" label_points_burn_down: "Down" label_points_burn_up: "Up" - label_product_backlog: "product backlog" - label_select_all: "Select all" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "sprint backlog" - label_sprint_cards: "Export cards" label_sprint_impediments: "Sprint Impediments" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Velocity %{velocity}, based on %{sprints} sprints with an average %{days} days" - label_stories: "Stories" - label_stories_tasks: "Stories/Tasks" label_task_board: "Task board" - label_version_setting: "संस्करण" - label_version: 'संस्करण' - label_webcal: "Webcal Feed" - label_wiki: "विकी" permission_view_master_backlog: "View master backlog" permission_view_taskboards: "View taskboards" permission_select_done_statuses: "Select done statuses" permission_update_sprints: "Update sprints" - points_accepted: "points accepted" - points_committed: "points committed" - points_resolved: "points resolved" - points_to_accept: "points not accepted" - points_to_resolve: "points not resolved" project_module_backlogs: "Backlogs" - rb_label_copy_tasks: "Copy work packages" - rb_label_copy_tasks_all: "All" - rb_label_copy_tasks_none: "None" - rb_label_copy_tasks_open: "Open" - rb_label_link_to_original: "Include link to original story" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "remaining work" - required_burn_rate_hours: "required burn rate (hours)" - required_burn_rate_points: "required burn rate (points)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Column in backlog" version_settings_display_option_left: "left" version_settings_display_option_none: "कुछ नहीं" diff --git a/modules/backlogs/config/locales/crowdin/hr.yml b/modules/backlogs/config/locales/crowdin/hr.yml index 18fdb7ed871..c5038cf2468 100644 --- a/modules/backlogs/config/locales/crowdin/hr.yml +++ b/modules/backlogs/config/locales/crowdin/hr.yml @@ -25,6 +25,8 @@ hr: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Pozicija" story_points: "Točke priče" @@ -43,128 +45,99 @@ hr: attributes: task_type: "Task type" backlogs: - add_new_story: "Nova priča" any: "bilo koji" - backlog_settings: "Backlogs settings" - burndown_graph: "Burndown graf" - card_paper_size: "Format papira za ispis kartice" - chart_options: "Mogućnosti grafikona" - close: "Zatvori" - column_width: "Širina stupca:" - date: "Dan" + column_width: "Column width" definition_of_done: "Definicija učinjenog" - generating_chart: "Generiram Graf..." - hours: "Sati" impediment: "Teškoće" label_versions_default_fold_state: "Prikaži prikupljene verzije" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." work_package_is_closed: "Radni zadatak je urađen, kada" label_is_done_status: "Status %{status_name} je završen" - no_burndown_data: "Ne postoje dostupni burndown podaci. Neophodno je imati početni i krajnji datum perioda razvoja." - points: "Bodovi" + points_label: + one: "point" + few: "points" + other: "points" positions_could_not_be_rebuilt: "Nije u mogućnosti napraviti obnavljanje pozicija." positions_rebuilt_successfully: "Pozicije obnovljena uspješno." - properties: "Postavke" rebuild: "Obnovi" rebuild_positions: "Obnovi pozicije" remaining_hours: "Remaining work" - remaining_hours_ideal: "Remaining work (ideal)" show_burndown_chart: "Burndown graf" story: "Scenarij" - story_points: "Točke priče" - story_points_ideal: "Točke scenarija (idealno)" + story_points: + one: "%{count} story point" + few: "%{count} story points" + other: "%{count} story points" task: "Zadaća" task_color: "Boja zadaće" unassigned: "Nedodijeljeno" user_preference: header_backlogs: "Backlogs module" button_update_backlogs: "Update backlogs module" - x_more: "%{count} više..." - backlogs_active: "aktivno" - backlogs_any: "bilo koji" - backlogs_inactive: "Projekt trenutno nema aktivnosti" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Burn točke gore/dole" backlogs_product_backlog: "Backlog produkta" - backlogs_product_backlog_is_empty: "Backlog produkta je prazan" - backlogs_product_backlog_unsized: "Velika većina backlog zapisa produkta ima ne dimenzionirane scenarije" - backlogs_sizing_inconsistent: "Veličina scenarija varira u odnosu na procijenu" - backlogs_sprint_notes_missing: "Zaključeni periodi razvoja bez retroaktivnih/razmotrenih bilješki" - backlogs_sprint_unestimated: "Zaključeni ili aktivni periodi razvoja s predviđenim scenarijima" - backlogs_sprint_unsized: "Projekt ne sadrži scenarije na aktivnim ili nedavno zaključenim periodima razvoja koji nisu dimenzionirani" - backlogs_sprints: "Periodi razvoja" backlogs_story: "Scenarij" backlogs_story_type: "Tipovi scenarija" backlogs_task: "Zadatak" backlogs_task_type: "Tip zadaće" - backlogs_velocity_missing: "Brzina ne može biti izračunata za ovaj projekt" - backlogs_velocity_varies: "Brzina se značajno razlikuje u odnosu na periode razvoja" backlogs_wiki_template: "Predlošci peridoa razvoja za wiki stranicu" - backlogs_empty_title: "No versions are defined to be used in backlogs" - backlogs_empty_action_text: "To get started with backlogs, create a new version and assign it to a backlogs column." - button_edit_wiki: "Uredi wiki stranicu" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "Nastupile su sljedeće pogreške:" - error_intro_singular: "Nastupila je sljedeća pogreška:" - error_outro: "Molim Vas ispravite gore navedene greške prije nego opet potvrdite unose." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "idealno" - inclusion: "nije uključeno u popis" - label_back_to_project: "Povratak na stranicu projekta" - label_backlog: "Backlog" label_backlogs: "Backlogs" label_backlogs_unconfigured: "Niste još konfigurirali Backlog. Za konfiguraciju odaberite %{administration}>%{plugins}, zatim %{configure} Backlog dodatak. Nakon što ste uredili potrebna polja, vratite se na ovu stranicu da biste započeli s korištenjem ovog alata." label_blocks_ids: "ID blokiranih radnih paketa" - label_burndown: "Burndown" label_column_in_backlog: "Stupac u backlogu" - label_hours: "sati" - label_work_package_hierarchy: "Hijerarhija radnih paketa" - label_master_backlog: "Glavni Backlog" - label_not_prioritized: "nije prioritet" - label_points: "bodovi" label_points_burn_down: "Dolje" label_points_burn_up: "Gore" - label_product_backlog: "backlog produkta" - label_select_all: "Odaberi Sve" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "sprint backlog" - label_sprint_cards: "Izvezi sprint kartice" label_sprint_impediments: "Prepreke perioda razvoja" - label_sprint_name: "Period razvoja \"%{name}\"" - label_sprint_velocity: "Brzina %{velocity}, na temelju %{sprints} sprintova s prosječno %{days} dana" - label_stories: "Scenariji" - label_stories_tasks: "Scenariji/Zadaci" label_task_board: "Upravitelj zadatcima" - label_version_setting: "Verzije" - label_version: 'Verzija' - label_webcal: "Webcal Feed" - label_wiki: "Wiki" permission_view_master_backlog: "Pogledaj glavni backlog" permission_view_taskboards: "Pogledaj upravitelj zadatcima" permission_select_done_statuses: "Select done statuses" permission_update_sprints: "Ažuriraj periode razvoja" - points_accepted: "točaka prihvaćeno" - points_committed: "točaka commitano" - points_resolved: "točaka riješeno" - points_to_accept: "točke nisu prihvaćene" - points_to_resolve: "točke za rješavanje" project_module_backlogs: "Backlogs" - rb_label_copy_tasks: "Kopiraj radne pakete" - rb_label_copy_tasks_all: "Svi" - rb_label_copy_tasks_none: "Nijedan" - rb_label_copy_tasks_open: "Otvoren" - rb_label_link_to_original: "Uključi poveznicu u orginalni scenarij" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "remaining work" - required_burn_rate_hours: "potrebni burn omjer (sati)" - required_burn_rate_points: "potrebni burn omjer (točke)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Stupac u backlogu" version_settings_display_option_left: "lijevo" version_settings_display_option_none: "nijedan" diff --git a/modules/backlogs/config/locales/crowdin/hu.yml b/modules/backlogs/config/locales/crowdin/hu.yml index b2ef6b38dfe..9f037ddac77 100644 --- a/modules/backlogs/config/locales/crowdin/hu.yml +++ b/modules/backlogs/config/locales/crowdin/hu.yml @@ -25,6 +25,8 @@ hu: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Pozició" story_points: "Story pontok" @@ -43,128 +45,97 @@ hu: attributes: task_type: "Feladat típusa" backlogs: - add_new_story: "Új story" any: "bármely" - backlog_settings: "Backlog beállítások" - burndown_graph: "Burndown ábra" - card_paper_size: "Papír méret kártya nyomtatáshoz" - chart_options: "Chart options" - close: "Bezár" - column_width: "Oszlopszélesség:" - date: "Nap" + column_width: "Column width" definition_of_done: "A \"Kész\" meghatározása" - generating_chart: "Ábra létrehozása..." - hours: "Óra" impediment: "Akadály" label_versions_default_fold_state: "Összecsukott verziók mutatása" caption_versions_default_fold_state: "A verziók alapértelmezés szerint nem lesznek kibontva a várólista (backlog) megtekintésekor. Mindegyiket manuálisan kell kibontani." work_package_is_closed: "A munkacsomag készen áll, ekkor" label_is_done_status: "Státusz %{status_name} kész állapotot jelöl" - no_burndown_data: "Nincs megjeleníthető napi teendő adat. Szükség van a sprint kezdési és befejezési időpontjára." - points: "Pontok" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "A pozíciókat nem lehet újraépíteni." positions_rebuilt_successfully: "A pozíciók újraépítése sikerült." - properties: "Tulajdonságok" rebuild: "Újraépítés" rebuild_positions: "Pozíciók újraépítése" remaining_hours: "Hátralévő munka" - remaining_hours_ideal: "Fennmaradó órák (ideális)" show_burndown_chart: "Napi teendő ábra" story: "Sztori" - story_points: "Story pontok" - story_points_ideal: "Sztori pontok (ideális)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "Feladat" task_color: "Feladat színe" unassigned: "Nincs hozzárendelés" user_preference: header_backlogs: "Várólista modul" button_update_backlogs: "Várólista modul frissítése" - x_more: "további %{count}..." - backlogs_active: "aktív" - backlogs_any: "bármely" - backlogs_inactive: "A projekt nem mutat tevékenységet" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Pontok teendő fel/le" backlogs_product_backlog: "Termék backlog" - backlogs_product_backlog_is_empty: "Termék backlog üres" - backlogs_product_backlog_unsized: "The top of the product backlog has unsized stories" - backlogs_sizing_inconsistent: "Story sizes vary against their estimates" - backlogs_sprint_notes_missing: "Closed sprints without retrospective/review notes" - backlogs_sprint_unestimated: "Closed or active sprints with unestimated stories" - backlogs_sprint_unsized: "Project has stories on active or recently closed sprints that were not sized" - backlogs_sprints: "Sprints" backlogs_story: "Sztori" backlogs_story_type: "Story types" backlogs_task: "Feladat" backlogs_task_type: "Task type" - backlogs_velocity_missing: "No velocity could be calculated for this project" - backlogs_velocity_varies: "Velocity varies significantly over sprints" backlogs_wiki_template: "Template for sprint wiki page" - backlogs_empty_title: "No versions are defined to be used in backlogs" - backlogs_empty_action_text: "To get started with backlogs, create a new version and assign it to a backlogs column." - button_edit_wiki: "Edit wiki page" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "nem lehet egyúttal történet típus is" - error_intro_plural: "The following errors were encountered:" - error_intro_singular: "The following error was encountered:" - error_outro: "Please correct the above errors before submitting again." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ideális" - inclusion: "nem szerepel a listán" - label_back_to_project: "Back to project page" - label_backlog: "Elvégzendő feladatok" label_backlogs: "Backlogs" label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "IDs of blocked work packages" - label_burndown: "Burndown" label_column_in_backlog: "Column in backlog" - label_hours: "hours" - label_work_package_hierarchy: "Work package Hierarchy" - label_master_backlog: "Master Backlog" - label_not_prioritized: "not prioritized" - label_points: "points" label_points_burn_down: "Le" label_points_burn_up: "Fel" - label_product_backlog: "termék backlog" - label_select_all: "Összes kijelölése" label_select_type: "Válasszon típust" label_select_types: "Típusok kiválasztása" label_selected_type: "Kiválasztott típus" label_selected_types: "Kiválasztott típusok" - label_sprint_backlog: "sprint backlog" - label_sprint_cards: "Kártyák exportálása" label_sprint_impediments: "Sprint akadályai" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Velocity %{velocity}, based on %{sprints} sprints with an average %{days} days" - label_stories: "Storyk" - label_stories_tasks: "Storyk/Feladatok" label_task_board: "Task board" - label_version_setting: "Versions" - label_version: 'Verzió' - label_webcal: "Webcal Feed" - label_wiki: "Wiki" permission_view_master_backlog: "View master backlog" permission_view_taskboards: "View taskboards" permission_select_done_statuses: "Elkészültek kiválasztása" permission_update_sprints: "Update sprints" - points_accepted: "points accepted" - points_committed: "points committed" - points_resolved: "points resolved" - points_to_accept: "points not accepted" - points_to_resolve: "points not resolved" project_module_backlogs: "Backlogs" - rb_label_copy_tasks: "Copy work packages" - rb_label_copy_tasks_all: "Mind" - rb_label_copy_tasks_none: "None" - rb_label_copy_tasks_open: "Open" - rb_label_link_to_original: "Include link to original story" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "Fennmaradó órák" - required_burn_rate_hours: "required burn rate (hours)" - required_burn_rate_points: "required burn rate (points)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Column in backlog" version_settings_display_option_left: "balra" version_settings_display_option_none: "none" diff --git a/modules/backlogs/config/locales/crowdin/id.yml b/modules/backlogs/config/locales/crowdin/id.yml index 53073bec9da..bd19ce678dc 100644 --- a/modules/backlogs/config/locales/crowdin/id.yml +++ b/modules/backlogs/config/locales/crowdin/id.yml @@ -25,6 +25,8 @@ id: description: "Modul ini menambahkan fitur yang memungkinkan tim yang gesit bekerja dengan OpenProject dalam proyek Scrum." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Posisi" story_points: "Cerita poin" @@ -43,128 +45,95 @@ id: attributes: task_type: "Jenis tugas" backlogs: - add_new_story: "Cerita baru " any: "apapun" - backlog_settings: "Pengaturan Backlog" - burndown_graph: "Grafik Burndown" - card_paper_size: "Ukuran kertas untuk pencetakan kartu" - chart_options: "Bagan pilihan" - close: "Dekat" - column_width: "Lebar kolom: " - date: "Hari" + column_width: "Column width" definition_of_done: "Definisi Selesai" - generating_chart: "Menghasilkan Grafik..." - hours: "Jam" impediment: "Halangan" label_versions_default_fold_state: "Tampilkan versi terlipat" caption_versions_default_fold_state: "Versi tidak akan diperluas secara default saat melihat daftar tugas yang tertunda. Setiap versi harus diperluas secara manual." work_package_is_closed: "Paket kerja selesai, ketika" label_is_done_status: "Status %{status_name} berarti selesai" - no_burndown_data: "Tidak ada data burndown yang tersedia. Hal ini diperlukan untuk memiliki tanggal mulai dan akhir sprint yang ditetapkan." - points: "Poin" + points_label: + other: "points" positions_could_not_be_rebuilt: "Posisi tidak dapat dibangun kembali." positions_rebuilt_successfully: "Posisi berhasil dibangun kembali." - properties: "Properti" rebuild: "Membangun kembali" rebuild_positions: "Membangun posisi kembali" remaining_hours: "Pekerjaan yang tersisa" - remaining_hours_ideal: "Pekerjaan yang tersisa (ideal)" show_burndown_chart: "Burndown grafik" story: "Cerita" - story_points: "Cerita poin" - story_points_ideal: "Cerita poin (ideal)" + story_points: + other: "%{count} story points" task: "Tugas" task_color: "Warna tugas" unassigned: "Belum ditetapkan" user_preference: header_backlogs: "Modul backlog" button_update_backlogs: "Perbarui modul backlog" - x_more: "%{count} lebih ..." - backlogs_active: "aktif" - backlogs_any: "apapun" - backlogs_inactive: "Proyek tidak menunjukkan aktivitas" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Poin terbakar habis / turun" backlogs_product_backlog: "Backlog produk" - backlogs_product_backlog_is_empty: "Produk backlog kosong" - backlogs_product_backlog_unsized: "Bagian atas backlog produk memiliki cerita yang tidak beraneka ragam" - backlogs_sizing_inconsistent: "Ukuran cerita bervariasi terhadap perkiraan mereka" - backlogs_sprint_notes_missing: "Sprint tertutup tanpa catatan retrospektif / ulasan" - backlogs_sprint_unestimated: "Tertutup atau sprint aktif dengan cerita yang tidak beralasan" - backlogs_sprint_unsized: "Proyek memiliki cerita tentang sprint aktif atau baru tertutup yang tidak berukuran" - backlogs_sprints: "Sprint" backlogs_story: "Cerita" backlogs_story_type: "Jenis cerita" backlogs_task: "Tugas" backlogs_task_type: "Jenis tugas" - backlogs_velocity_missing: "Tidak ada kecepatan yang bisa dihitung untuk proyek ini" - backlogs_velocity_varies: "Kecepatan bervariasi secara signifikan selama sprint" backlogs_wiki_template: "Template untuk halaman wiki sprint" - backlogs_empty_title: "Ada versi yang didefinisikan untuk digunakan dalam backlogs" - backlogs_empty_action_text: "Untuk memulai dengan backlogs, membuat versi baru, dan menetapkannya ke kolom backlogs." - button_edit_wiki: "Edit halaman wiki" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "bisa juga bukan jenis cerita" - error_intro_plural: "Kesalahan berikut ini ditemui:" - error_intro_singular: "Kesalahan berikut ini ditemui:" - error_outro: "Perbaiki kesalahan di atas sebelum mengirimkannya lagi." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ideal" - inclusion: "tidak termasuk dalam daftar" - label_back_to_project: "Kembali ke halaman proyek" - label_backlog: "Jaminan simpanan" label_backlogs: "Backlogs" label_backlogs_unconfigured: "Anda belum mengkonfigurasi Backlogs. Silakan masuk ke %{administration}> %{plugins}, lalu klik pada link %{configure} untuk plugin ini. Setelah Anda menyetel bidang, kembali ke halaman ini untuk mulai menggunakan alat ini." label_blocks_ids: "ID dari paket pekerjaan yang diblokir" - label_burndown: "Burndown" label_column_in_backlog: "Kolom di backlog" - label_hours: "jamb" - label_work_package_hierarchy: "Hirarki paket kerja" - label_master_backlog: "Master Backlog" - label_not_prioritized: "tidak diprioritaskan" - label_points: "poin " label_points_burn_down: "Menurun" label_points_burn_up: "Naik" - label_product_backlog: "backlog produk" - label_select_all: "Pilih Semua" label_select_type: "Pilih jenis" label_select_types: "Pilih jenis" label_selected_type: "Jenis yang dipilih" label_selected_types: "Jenis yang dipilih" - label_sprint_backlog: "sprint backlog" - label_sprint_cards: "Kartu ekspor" label_sprint_impediments: "Hanbatan kekuatan" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Kecepatan %{velocity}, berdasarkan %{sprints} sprint dengan rata-rata %{days} days" - label_stories: "Cerita" - label_stories_tasks: "Cerita / Tugas" label_task_board: "Papan tugas" - label_version_setting: "Versi" - label_version: 'Versi' - label_webcal: "Webcam Feed" - label_wiki: "Wiki" permission_view_master_backlog: "Lihat backlog master" permission_view_taskboards: "Lihat papan tugas" permission_select_done_statuses: "Pilih status selesai" permission_update_sprints: "Perbarui sprint" - points_accepted: "poin diterima" - points_committed: "poin yang dilakukan" - points_resolved: "poin terselesaikan" - points_to_accept: "poin tidak diterima" - points_to_resolve: "poin tidak terselesaikan" project_module_backlogs: "Backlogs" - rb_label_copy_tasks: "Salin paket kerja" - rb_label_copy_tasks_all: "Semua" - rb_label_copy_tasks_none: "Tidak ada" - rb_label_copy_tasks_open: "Buka" - rb_label_link_to_original: "Sertakan link ke cerita asli" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "pekerjaan yang tersisa" - required_burn_rate_hours: "tingkat pembakaran yang dibutuhkan (jam)" - required_burn_rate_points: "tingkat pembakaran yang dibutuhkan (poin)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Kolom di backlog" version_settings_display_option_left: "kiri" version_settings_display_option_none: "tidak ada" diff --git a/modules/backlogs/config/locales/crowdin/it.yml b/modules/backlogs/config/locales/crowdin/it.yml index b1026dfa4ea..16f1de330c9 100644 --- a/modules/backlogs/config/locales/crowdin/it.yml +++ b/modules/backlogs/config/locales/crowdin/it.yml @@ -25,6 +25,8 @@ it: description: "Questo modulo aggiunge funzionalità che consentono ai team agili di lavorare con i progetti OpenProject in Scrum." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Posizione" story_points: "Punti della storia" @@ -43,128 +45,97 @@ it: attributes: task_type: "Tipo di attività" backlogs: - add_new_story: "Nuova storia" any: "qualsiasi" - backlog_settings: "Impostazioni di backlog" - burndown_graph: "Grafico Burndown" - card_paper_size: "Formato della carta per la stampa delle schede" - chart_options: "Opzioni grafico" - close: "Chiuso" - column_width: "Larghezza della colonna:" - date: "Giorno" + column_width: "Column width" definition_of_done: "Definizione di fatto" - generating_chart: "Grafico in generazione..." - hours: "Ore" impediment: "Impedimento" label_versions_default_fold_state: "Espandi le versioni" caption_versions_default_fold_state: "Le versioni non verranno espanse per impostazione predefinita durante la visualizzazione dei backlog. Ogni versione deve essere espansa manualmente." work_package_is_closed: "Il pacchetto di lavoro è fatto, quando" label_is_done_status: "Lo stato %{status_name} vuol dire completato" - no_burndown_data: "Non sono disponibili i dati del burndown. È necessario avere impostato le date di inizio e fine dello sprint." - points: "Punti" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "Le posizioni non potrebbero essere ricostruite." positions_rebuilt_successfully: "Posizioni ricostruite correttamente." - properties: "Proprietà" rebuild: "Ricostruisci" rebuild_positions: "Ricostruisce le posizioni" remaining_hours: "Lavoro residuo" - remaining_hours_ideal: "Lavoro residuo (ideale)" show_burndown_chart: "Grafico Burndown" story: "Storia" - story_points: "Punti della storia" - story_points_ideal: "Punti della storia (ideale)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "Attività" task_color: "Colore attività" unassigned: "Non assegnato" user_preference: header_backlogs: "Modulo backlog" button_update_backlogs: "Aggiorna il modulo backlog" - x_more: "%{count} più..." - backlogs_active: "attivo" - backlogs_any: "qualsiasi" - backlogs_inactive: "Progetto non mostra alcuna attività" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Punteggi per burn positivo/negativo" backlogs_product_backlog: "Backlog del prodotto" - backlogs_product_backlog_is_empty: "Il backlog prodotto è vuoto" - backlogs_product_backlog_unsized: "In cima al backlog di prodotto vi sono story non quantificate" - backlogs_sizing_inconsistent: "Le dimensioni della storia variano rispetto le loro stime" - backlogs_sprint_notes_missing: "Sprint chiusi senza note retrospettive/recensioni" - backlogs_sprint_unestimated: "Sprint chiusi o attivi con storie non quantificate" - backlogs_sprint_unsized: "Il progetto ha storie su sprint attivi o chiusi di recente che non sono stati quantificati" - backlogs_sprints: "Sprint" backlogs_story: "Storia" backlogs_story_type: "Tipi di storia" backlogs_task: "Attività" backlogs_task_type: "Tipo di attività" - backlogs_velocity_missing: "Per questo progetto non può essere calcolata la velocità" - backlogs_velocity_varies: "La velocità del progetto varia in modo significativo tra gli sprint" backlogs_wiki_template: "Modello per pagina wiki dello sprint" - backlogs_empty_title: "Non è stata definita nessuna versione per i backlog" - backlogs_empty_action_text: "Per iniziare ad utilizzare i backlog, crea una nuova versione e assegnala ad una colonna nel backlog." - button_edit_wiki: "Modifica la pagina wiki" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "non può essere anche un tipo di storia" - error_intro_plural: "Si sono verificati i seguenti errori:" - error_intro_singular: "È stato rilevato il seguente errore:" - error_outro: "Si prega di correggere gli errori riportati prima di inviare nuovamente." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ideale" - inclusion: "non è incluso nell'elenco" - label_back_to_project: "Torna alla pagina del progetto" - label_backlog: "Backlog" label_backlogs: "Backlog" label_backlogs_unconfigured: "Non hai ancora configurato i Backlog. Vai su %{administration} > %{plugins}, quindi fai clic sul link %{configure} per il plugin. Dopo aver impostato i campi, torna su questa pagina per iniziare a utilizzare lo strumento." label_blocks_ids: "ID dei pacchetti di lavoro bloccati" - label_burndown: "Burndown" label_column_in_backlog: "Colonna nel backlog" - label_hours: "ore" - label_work_package_hierarchy: "Gerarchia dei pacchetto di lavoro" - label_master_backlog: "Master Backlog" - label_not_prioritized: "priorità non definita" - label_points: "punti" label_points_burn_down: "Verso il basso" label_points_burn_up: "Verso l'alto" - label_product_backlog: "backlog del prodotto" - label_select_all: "Seleziona tutto" label_select_type: "Seleziona un tipo" label_select_types: "Seleziona i tipi" label_selected_type: "Tipo selezionato" label_selected_types: "Tipi selezionati" - label_sprint_backlog: "backlog di sprint" - label_sprint_cards: "Esporta schede" label_sprint_impediments: "Impedimenti allo sprint" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "La velocity %{velocity}, basato su %{sprints} Sprint di %{days} giorni in media" - label_stories: "Storie" - label_stories_tasks: "Storie/Attività" label_task_board: "Pannello delle attività" - label_version_setting: "Versioni" - label_version: 'Versione' - label_webcal: "Webcal Feed" - label_wiki: "Wiki" permission_view_master_backlog: "Visualizza il master backlog" permission_view_taskboards: "Visualizza i pannelli delle attività" permission_select_done_statuses: "Seleziona gli stati terminati" permission_update_sprints: "Aggiorna gli sprint" - points_accepted: "punti accettati" - points_committed: "punti acquisiti" - points_resolved: "punti risolti" - points_to_accept: "punti non accettati" - points_to_resolve: "punti non risolti" project_module_backlogs: "Backlog" - rb_label_copy_tasks: "Copia i pacchetti di lavoro" - rb_label_copy_tasks_all: "Tutti" - rb_label_copy_tasks_none: "Nessuno" - rb_label_copy_tasks_open: "Aperti" - rb_label_link_to_original: "Include il link alla storia originale" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "lavoro residuo" - required_burn_rate_hours: "burn rate richiesto (ore)" - required_burn_rate_points: "burn rate richiesto (punti)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Colonna nel backlog" version_settings_display_option_left: "sinistra" version_settings_display_option_none: "nessuno" diff --git a/modules/backlogs/config/locales/crowdin/ja.yml b/modules/backlogs/config/locales/crowdin/ja.yml index c5589093ff4..3ed3aea3394 100644 --- a/modules/backlogs/config/locales/crowdin/ja.yml +++ b/modules/backlogs/config/locales/crowdin/ja.yml @@ -25,6 +25,8 @@ ja: description: "このモジュールには、アジャイルチームがスクラムプロジェクトでOpenProjectを使用できるようにする機能が追加されています。" activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "位置" story_points: "ストーリーポイント" @@ -43,128 +45,95 @@ ja: attributes: task_type: "タスクのタイプ" backlogs: - add_new_story: "新しいストーリー" any: "全て" - backlog_settings: "バックログの設定" - burndown_graph: "バーンダウングラフ" - card_paper_size: "カード印刷用の用紙サイズ" - chart_options: "チャートオプション" - close: "終了する" - column_width: "列の幅:" - date: "日" + column_width: "Column width" definition_of_done: "「終了」の定義" - generating_chart: "グラフを生成中..." - hours: "時間" impediment: "障害事項" label_versions_default_fold_state: "バージョンを折り畳んで表示" caption_versions_default_fold_state: "バックログを表示する場合、デフォルトではバージョンは展開されません。各バージョンは手動で展開する必要があります。" work_package_is_closed: "ワークパッケージが終了するには" label_is_done_status: "ステータス%{status_name}は完了として扱う" - no_burndown_data: "バーンダウンデータがありません。スプリントの開始日と終了日を設定する必要があります。" - points: "ポイント" + points_label: + other: "points" positions_could_not_be_rebuilt: "位置は再構築されませんでした。" positions_rebuilt_successfully: "位置情報は再構築しました。" - properties: "プロパティ" rebuild: "再構築" rebuild_positions: "位置情報を再構築" remaining_hours: "残時間" - remaining_hours_ideal: "残時間(計画)" show_burndown_chart: "バーンダウングラフ" story: "ストーリー" - story_points: "ストーリーポイント" - story_points_ideal: "ストーリーポイント (理想)" + story_points: + other: "%{count} story points" task: "タスク" task_color: "タスクの色" unassigned: "未割り当て" user_preference: header_backlogs: "バックログモジュール" button_update_backlogs: "バックログモジュールを更新" - x_more: "残り%{count}件…" - backlogs_active: "アクティブ" - backlogs_any: "全て" - backlogs_inactive: "プロジェクトの活動がありません。" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "ポイントのバーンアップ/バーンダウン" backlogs_product_backlog: "プロダクトバックログ" - backlogs_product_backlog_is_empty: "プロダクトバックログはありません。" - backlogs_product_backlog_unsized: "プロダクトバックログの最上部にはサイズ化されてないストーリーがあります。" - backlogs_sizing_inconsistent: "ストーリーのサイズは見積もりと異なります。" - backlogs_sprint_notes_missing: "反復振り返り/レビューのメモなしで閉じたスプリント" - backlogs_sprint_unestimated: "見積もってないストーリーを持つアクティブなもしくはクローズされたスプリント" - backlogs_sprint_unsized: "プロジェクトはアクティブなもしくは最近クローズされたスプリントに対しサイズが合っていないストーリーを含んでいます" - backlogs_sprints: "スプリント" backlogs_story: "ストーリー" backlogs_story_type: "ストーリーの種類" backlogs_task: "タスク" backlogs_task_type: "タスクの種類" - backlogs_velocity_missing: "このプロジェクトのベロシティを計算できなかった" - backlogs_velocity_varies: "ベロシティがスプリントと大幅に異なります" backlogs_wiki_template: "スプリントのWikiページのテンプレート" - backlogs_empty_title: "バックログで使用するバージョンは定義されていません" - backlogs_empty_action_text: "バックログを開始するには、新しいバージョンを作成しそれをバックログ列に割り当てます。" - button_edit_wiki: "Wikiページの編集" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "次のエラーが発生しました:" - error_intro_singular: "次のエラーが発生しました:" - error_outro: "送信する前に上記のエラーを修正してください。" - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "理想時間" - inclusion: "はリストに含まれていません。" - label_back_to_project: "プロジェクトページに戻る" - label_backlog: "バックログ" label_backlogs: "バックログ" label_backlogs_unconfigured: "バックログは未設定です。%{administration} > %{plugins}をアクセスして、このプラグインの%{configure}リンクをクリックしてください。フィールドを設定した後、このページに戻ってツールを使用開始してください。" label_blocks_ids: "ブロックされているワークパッケージのID" - label_burndown: "バーンダウン" label_column_in_backlog: "バックログの列" - label_hours: "時間" - label_work_package_hierarchy: "ワークパッケージの階層" - label_master_backlog: "マスターバックログ" - label_not_prioritized: "優先度が未設定" - label_points: "ポイント" label_points_burn_down: "ダウン" label_points_burn_up: "アップ" - label_product_backlog: "プロダクトバックログ" - label_select_all: "全てを選択" label_select_type: "タイプを選択" label_select_types: "タイプを選択" label_selected_type: "タイプを選択" label_selected_types: "タイプを選択" - label_sprint_backlog: "スプリントバックログ" - label_sprint_cards: "カードをエクスポート" label_sprint_impediments: "スプリント障害事項" - label_sprint_name: "スプリント\"%{name}\"" - label_sprint_velocity: "平均 %{days} 日 を持つ%{sprints}スプリントに基づく%{velocity}ベロシティ" - label_stories: "ストーリー" - label_stories_tasks: "ストーリー/タスク" label_task_board: "かんばん" - label_version_setting: "バージョン" - label_version: 'バージョン' - label_webcal: "Webcal フィード" - label_wiki: "Wiki" permission_view_master_backlog: "マスター バックログの表示" permission_view_taskboards: "かんばんの表示" permission_select_done_statuses: "完了ステータスを選択" permission_update_sprints: "スプリントの更新" - points_accepted: "進行中のポイント" - points_committed: "承認されたポイント" - points_resolved: "完了したポイント" - points_to_accept: "未着手ポイント" - points_to_resolve: "未解決ポイント" project_module_backlogs: "バックログ" - rb_label_copy_tasks: "ワークパッケージをコピー" - rb_label_copy_tasks_all: "全て" - rb_label_copy_tasks_none: "なし" - rb_label_copy_tasks_open: "開く" - rb_label_link_to_original: "元のストーリーへのリンクを含める" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "残時間" - required_burn_rate_hours: "必要なバーンレート(時間)" - required_burn_rate_points: "必要なバーンレート(ポイント)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "バックログの列" version_settings_display_option_left: "左へ" version_settings_display_option_none: "なし" diff --git a/modules/backlogs/config/locales/crowdin/js-af.yml b/modules/backlogs/config/locales/crowdin/js-af.yml index b8fc376b76a..c8117ea559b 100644 --- a/modules/backlogs/config/locales/crowdin/js-af.yml +++ b/modules/backlogs/config/locales/crowdin/js-af.yml @@ -24,3 +24,6 @@ af: work_packages: properties: storyPoints: "Storie Punte" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-ar.yml b/modules/backlogs/config/locales/crowdin/js-ar.yml index 3d7b0263b12..a036138417b 100644 --- a/modules/backlogs/config/locales/crowdin/js-ar.yml +++ b/modules/backlogs/config/locales/crowdin/js-ar.yml @@ -24,3 +24,6 @@ ar: work_packages: properties: storyPoints: "نقاط القصة" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-az.yml b/modules/backlogs/config/locales/crowdin/js-az.yml index 1c9693c1e7a..6b36f184c21 100644 --- a/modules/backlogs/config/locales/crowdin/js-az.yml +++ b/modules/backlogs/config/locales/crowdin/js-az.yml @@ -24,3 +24,6 @@ az: work_packages: properties: storyPoints: "Story Points" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-be.yml b/modules/backlogs/config/locales/crowdin/js-be.yml index 77032b44117..84f5c481c0f 100644 --- a/modules/backlogs/config/locales/crowdin/js-be.yml +++ b/modules/backlogs/config/locales/crowdin/js-be.yml @@ -24,3 +24,6 @@ be: work_packages: properties: storyPoints: "Story Points" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-bg.yml b/modules/backlogs/config/locales/crowdin/js-bg.yml index 4071cc5061e..18268209a8a 100644 --- a/modules/backlogs/config/locales/crowdin/js-bg.yml +++ b/modules/backlogs/config/locales/crowdin/js-bg.yml @@ -24,3 +24,6 @@ bg: work_packages: properties: storyPoints: "Точки на история" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-ca.yml b/modules/backlogs/config/locales/crowdin/js-ca.yml index c15dc95d083..a371ac848ed 100644 --- a/modules/backlogs/config/locales/crowdin/js-ca.yml +++ b/modules/backlogs/config/locales/crowdin/js-ca.yml @@ -24,3 +24,6 @@ ca: work_packages: properties: storyPoints: "Punts d'història" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-ckb-IR.yml b/modules/backlogs/config/locales/crowdin/js-ckb-IR.yml index d40fb4a4403..6bf6905096e 100644 --- a/modules/backlogs/config/locales/crowdin/js-ckb-IR.yml +++ b/modules/backlogs/config/locales/crowdin/js-ckb-IR.yml @@ -24,3 +24,6 @@ ckb-IR: work_packages: properties: storyPoints: "Story Points" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-cs.yml b/modules/backlogs/config/locales/crowdin/js-cs.yml index 2e3fab72ab4..49a5f031b0b 100644 --- a/modules/backlogs/config/locales/crowdin/js-cs.yml +++ b/modules/backlogs/config/locales/crowdin/js-cs.yml @@ -24,3 +24,6 @@ cs: work_packages: properties: storyPoints: "Body příběhu" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-da.yml b/modules/backlogs/config/locales/crowdin/js-da.yml index cc1899f75fe..b46216459be 100644 --- a/modules/backlogs/config/locales/crowdin/js-da.yml +++ b/modules/backlogs/config/locales/crowdin/js-da.yml @@ -24,3 +24,6 @@ da: work_packages: properties: storyPoints: "Historiepunkter" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-de.yml b/modules/backlogs/config/locales/crowdin/js-de.yml index cf46bacc65a..55df96298af 100644 --- a/modules/backlogs/config/locales/crowdin/js-de.yml +++ b/modules/backlogs/config/locales/crowdin/js-de.yml @@ -24,3 +24,6 @@ de: work_packages: properties: storyPoints: "Story Punkte" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-el.yml b/modules/backlogs/config/locales/crowdin/js-el.yml index c4793ce64f1..e86677920b1 100644 --- a/modules/backlogs/config/locales/crowdin/js-el.yml +++ b/modules/backlogs/config/locales/crowdin/js-el.yml @@ -24,3 +24,6 @@ el: work_packages: properties: storyPoints: "Πόντοι Ιστορίας" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-eo.yml b/modules/backlogs/config/locales/crowdin/js-eo.yml index 7a9ff33baf6..d14b8dd57ef 100644 --- a/modules/backlogs/config/locales/crowdin/js-eo.yml +++ b/modules/backlogs/config/locales/crowdin/js-eo.yml @@ -24,3 +24,6 @@ eo: work_packages: properties: storyPoints: "Historiaj poentoj" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-es.yml b/modules/backlogs/config/locales/crowdin/js-es.yml index 7def7838f7b..833e0141322 100644 --- a/modules/backlogs/config/locales/crowdin/js-es.yml +++ b/modules/backlogs/config/locales/crowdin/js-es.yml @@ -24,3 +24,6 @@ es: work_packages: properties: storyPoints: "Puntos de Historia" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-et.yml b/modules/backlogs/config/locales/crowdin/js-et.yml index 914c9ac4ab1..2ed95b26c2d 100644 --- a/modules/backlogs/config/locales/crowdin/js-et.yml +++ b/modules/backlogs/config/locales/crowdin/js-et.yml @@ -24,3 +24,6 @@ et: work_packages: properties: storyPoints: "Story Points" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-eu.yml b/modules/backlogs/config/locales/crowdin/js-eu.yml index b8e56471908..58bb6cd522e 100644 --- a/modules/backlogs/config/locales/crowdin/js-eu.yml +++ b/modules/backlogs/config/locales/crowdin/js-eu.yml @@ -24,3 +24,6 @@ eu: work_packages: properties: storyPoints: "Story Points" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-fa.yml b/modules/backlogs/config/locales/crowdin/js-fa.yml index bcf228b1d1c..709cfd1a830 100644 --- a/modules/backlogs/config/locales/crowdin/js-fa.yml +++ b/modules/backlogs/config/locales/crowdin/js-fa.yml @@ -24,3 +24,6 @@ fa: work_packages: properties: storyPoints: "وزن دهی کار" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-fi.yml b/modules/backlogs/config/locales/crowdin/js-fi.yml index 8160b5db8c8..b86129d8a96 100644 --- a/modules/backlogs/config/locales/crowdin/js-fi.yml +++ b/modules/backlogs/config/locales/crowdin/js-fi.yml @@ -24,3 +24,6 @@ fi: work_packages: properties: storyPoints: "Tarinapisteet" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-fil.yml b/modules/backlogs/config/locales/crowdin/js-fil.yml index 78c3f80cd5d..7f7536559c4 100644 --- a/modules/backlogs/config/locales/crowdin/js-fil.yml +++ b/modules/backlogs/config/locales/crowdin/js-fil.yml @@ -24,3 +24,6 @@ fil: work_packages: properties: storyPoints: "Story Points" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-fr.yml b/modules/backlogs/config/locales/crowdin/js-fr.yml index beec602b858..ead159b3f7a 100644 --- a/modules/backlogs/config/locales/crowdin/js-fr.yml +++ b/modules/backlogs/config/locales/crowdin/js-fr.yml @@ -24,3 +24,6 @@ fr: work_packages: properties: storyPoints: "Points d'histoire" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-he.yml b/modules/backlogs/config/locales/crowdin/js-he.yml index 6cf4e731743..137d66c0d31 100644 --- a/modules/backlogs/config/locales/crowdin/js-he.yml +++ b/modules/backlogs/config/locales/crowdin/js-he.yml @@ -24,3 +24,6 @@ he: work_packages: properties: storyPoints: "Story Points" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-hi.yml b/modules/backlogs/config/locales/crowdin/js-hi.yml index a47e0a48489..16ad03e87de 100644 --- a/modules/backlogs/config/locales/crowdin/js-hi.yml +++ b/modules/backlogs/config/locales/crowdin/js-hi.yml @@ -24,3 +24,6 @@ hi: work_packages: properties: storyPoints: "कहानी अंक" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-hr.yml b/modules/backlogs/config/locales/crowdin/js-hr.yml index 5aad08b2181..e770e53523c 100644 --- a/modules/backlogs/config/locales/crowdin/js-hr.yml +++ b/modules/backlogs/config/locales/crowdin/js-hr.yml @@ -24,3 +24,6 @@ hr: work_packages: properties: storyPoints: "Točke priče" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-hu.yml b/modules/backlogs/config/locales/crowdin/js-hu.yml index 225f886e8bc..43dd4fcc477 100644 --- a/modules/backlogs/config/locales/crowdin/js-hu.yml +++ b/modules/backlogs/config/locales/crowdin/js-hu.yml @@ -24,3 +24,6 @@ hu: work_packages: properties: storyPoints: "Story pontok" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-id.yml b/modules/backlogs/config/locales/crowdin/js-id.yml index 1258fc6e664..262f7d4e7ca 100644 --- a/modules/backlogs/config/locales/crowdin/js-id.yml +++ b/modules/backlogs/config/locales/crowdin/js-id.yml @@ -24,3 +24,6 @@ id: work_packages: properties: storyPoints: "Cerita point" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-it.yml b/modules/backlogs/config/locales/crowdin/js-it.yml index 99d6ac0178d..334cf2c3973 100644 --- a/modules/backlogs/config/locales/crowdin/js-it.yml +++ b/modules/backlogs/config/locales/crowdin/js-it.yml @@ -24,3 +24,6 @@ it: work_packages: properties: storyPoints: "Punti della storia" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-ja.yml b/modules/backlogs/config/locales/crowdin/js-ja.yml index 256ca023e9a..01ba60d3979 100644 --- a/modules/backlogs/config/locales/crowdin/js-ja.yml +++ b/modules/backlogs/config/locales/crowdin/js-ja.yml @@ -24,3 +24,6 @@ ja: work_packages: properties: storyPoints: "ストーリーポイント" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-ka.yml b/modules/backlogs/config/locales/crowdin/js-ka.yml index d6f7b04e8ab..2107137579e 100644 --- a/modules/backlogs/config/locales/crowdin/js-ka.yml +++ b/modules/backlogs/config/locales/crowdin/js-ka.yml @@ -24,3 +24,6 @@ ka: work_packages: properties: storyPoints: "ისტორიული წერტილები" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-kk.yml b/modules/backlogs/config/locales/crowdin/js-kk.yml index abd75354d77..9c11d979e80 100644 --- a/modules/backlogs/config/locales/crowdin/js-kk.yml +++ b/modules/backlogs/config/locales/crowdin/js-kk.yml @@ -24,3 +24,6 @@ kk: work_packages: properties: storyPoints: "Story Points" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-ko.yml b/modules/backlogs/config/locales/crowdin/js-ko.yml index 62d4fcfe871..34bf0f016c0 100644 --- a/modules/backlogs/config/locales/crowdin/js-ko.yml +++ b/modules/backlogs/config/locales/crowdin/js-ko.yml @@ -24,3 +24,6 @@ ko: work_packages: properties: storyPoints: "스토리 포인트" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-lt.yml b/modules/backlogs/config/locales/crowdin/js-lt.yml index e3eb91a50fb..144006742ea 100644 --- a/modules/backlogs/config/locales/crowdin/js-lt.yml +++ b/modules/backlogs/config/locales/crowdin/js-lt.yml @@ -24,3 +24,6 @@ lt: work_packages: properties: storyPoints: "Istorijos taškai" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-lv.yml b/modules/backlogs/config/locales/crowdin/js-lv.yml index f49b8011f93..56b0ee793a7 100644 --- a/modules/backlogs/config/locales/crowdin/js-lv.yml +++ b/modules/backlogs/config/locales/crowdin/js-lv.yml @@ -24,3 +24,6 @@ lv: work_packages: properties: storyPoints: "Novērtējums" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-mn.yml b/modules/backlogs/config/locales/crowdin/js-mn.yml index b50c387a617..8c95f52608c 100644 --- a/modules/backlogs/config/locales/crowdin/js-mn.yml +++ b/modules/backlogs/config/locales/crowdin/js-mn.yml @@ -24,3 +24,6 @@ mn: work_packages: properties: storyPoints: "Гүйцэтгэлийн оноо" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-ms.yml b/modules/backlogs/config/locales/crowdin/js-ms.yml index 0cba80c67d3..59f59266f86 100644 --- a/modules/backlogs/config/locales/crowdin/js-ms.yml +++ b/modules/backlogs/config/locales/crowdin/js-ms.yml @@ -24,3 +24,6 @@ ms: work_packages: properties: storyPoints: "Titik Cerita" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-ne.yml b/modules/backlogs/config/locales/crowdin/js-ne.yml index 544f3a9fde0..b64081d62c6 100644 --- a/modules/backlogs/config/locales/crowdin/js-ne.yml +++ b/modules/backlogs/config/locales/crowdin/js-ne.yml @@ -24,3 +24,6 @@ ne: work_packages: properties: storyPoints: "Story Points" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-nl.yml b/modules/backlogs/config/locales/crowdin/js-nl.yml index e9bf2571c9a..e4bf28c6bb3 100644 --- a/modules/backlogs/config/locales/crowdin/js-nl.yml +++ b/modules/backlogs/config/locales/crowdin/js-nl.yml @@ -24,3 +24,6 @@ nl: work_packages: properties: storyPoints: "Verhaal punten" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-no.yml b/modules/backlogs/config/locales/crowdin/js-no.yml index c58902f726d..6ef4a1213b2 100644 --- a/modules/backlogs/config/locales/crowdin/js-no.yml +++ b/modules/backlogs/config/locales/crowdin/js-no.yml @@ -24,3 +24,6 @@ work_packages: properties: storyPoints: "Historiepoeng" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-pl.yml b/modules/backlogs/config/locales/crowdin/js-pl.yml index 8ec3104280b..9f12530da91 100644 --- a/modules/backlogs/config/locales/crowdin/js-pl.yml +++ b/modules/backlogs/config/locales/crowdin/js-pl.yml @@ -24,3 +24,6 @@ pl: work_packages: properties: storyPoints: "Historia Punktów" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-pt-BR.yml b/modules/backlogs/config/locales/crowdin/js-pt-BR.yml index 483b09beba8..bb6d4d398bb 100644 --- a/modules/backlogs/config/locales/crowdin/js-pt-BR.yml +++ b/modules/backlogs/config/locales/crowdin/js-pt-BR.yml @@ -24,3 +24,6 @@ pt-BR: work_packages: properties: storyPoints: "Pontos de História" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-pt-PT.yml b/modules/backlogs/config/locales/crowdin/js-pt-PT.yml index 8eca2db4cb3..702c2f4368c 100644 --- a/modules/backlogs/config/locales/crowdin/js-pt-PT.yml +++ b/modules/backlogs/config/locales/crowdin/js-pt-PT.yml @@ -24,3 +24,6 @@ pt-PT: work_packages: properties: storyPoints: "Pontos de histórico" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-ro.yml b/modules/backlogs/config/locales/crowdin/js-ro.yml index ec3398de1ef..a908f3a3950 100644 --- a/modules/backlogs/config/locales/crowdin/js-ro.yml +++ b/modules/backlogs/config/locales/crowdin/js-ro.yml @@ -24,3 +24,6 @@ ro: work_packages: properties: storyPoints: "Puncte cerință" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-ru.yml b/modules/backlogs/config/locales/crowdin/js-ru.yml index dac673e3eca..4f7e851860a 100644 --- a/modules/backlogs/config/locales/crowdin/js-ru.yml +++ b/modules/backlogs/config/locales/crowdin/js-ru.yml @@ -24,3 +24,6 @@ ru: work_packages: properties: storyPoints: "Исторические точки" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-rw.yml b/modules/backlogs/config/locales/crowdin/js-rw.yml index e5dc1c47821..c05cfff5d7a 100644 --- a/modules/backlogs/config/locales/crowdin/js-rw.yml +++ b/modules/backlogs/config/locales/crowdin/js-rw.yml @@ -24,3 +24,6 @@ rw: work_packages: properties: storyPoints: "Story Points" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-si.yml b/modules/backlogs/config/locales/crowdin/js-si.yml index d4987425bfb..54a7ba64183 100644 --- a/modules/backlogs/config/locales/crowdin/js-si.yml +++ b/modules/backlogs/config/locales/crowdin/js-si.yml @@ -24,3 +24,6 @@ si: work_packages: properties: storyPoints: "කතන්දර කරුණු" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-sk.yml b/modules/backlogs/config/locales/crowdin/js-sk.yml index b416dfee108..6fb8db0c28c 100644 --- a/modules/backlogs/config/locales/crowdin/js-sk.yml +++ b/modules/backlogs/config/locales/crowdin/js-sk.yml @@ -24,3 +24,6 @@ sk: work_packages: properties: storyPoints: "História bodov" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-sl.yml b/modules/backlogs/config/locales/crowdin/js-sl.yml index 1424e30d513..e4dc1c880ee 100644 --- a/modules/backlogs/config/locales/crowdin/js-sl.yml +++ b/modules/backlogs/config/locales/crowdin/js-sl.yml @@ -24,3 +24,6 @@ sl: work_packages: properties: storyPoints: "Točke v zgodbi" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-sr.yml b/modules/backlogs/config/locales/crowdin/js-sr.yml index 405feee5975..76d7f79a597 100644 --- a/modules/backlogs/config/locales/crowdin/js-sr.yml +++ b/modules/backlogs/config/locales/crowdin/js-sr.yml @@ -24,3 +24,6 @@ sr: work_packages: properties: storyPoints: "Poeni Priče" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-sv.yml b/modules/backlogs/config/locales/crowdin/js-sv.yml index beae67dae19..4ad27a434ff 100644 --- a/modules/backlogs/config/locales/crowdin/js-sv.yml +++ b/modules/backlogs/config/locales/crowdin/js-sv.yml @@ -24,3 +24,6 @@ sv: work_packages: properties: storyPoints: "Berättelsepoäng" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-th.yml b/modules/backlogs/config/locales/crowdin/js-th.yml index 88684b8c435..e1e7fd1e9ab 100644 --- a/modules/backlogs/config/locales/crowdin/js-th.yml +++ b/modules/backlogs/config/locales/crowdin/js-th.yml @@ -24,3 +24,6 @@ th: work_packages: properties: storyPoints: "Story Points" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-tr.yml b/modules/backlogs/config/locales/crowdin/js-tr.yml index 34a962d06ae..50f9d616e4b 100644 --- a/modules/backlogs/config/locales/crowdin/js-tr.yml +++ b/modules/backlogs/config/locales/crowdin/js-tr.yml @@ -24,3 +24,6 @@ tr: work_packages: properties: storyPoints: "Hikaye Puanları" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-uk.yml b/modules/backlogs/config/locales/crowdin/js-uk.yml index ff8dc742cb5..aa86d45e2a2 100644 --- a/modules/backlogs/config/locales/crowdin/js-uk.yml +++ b/modules/backlogs/config/locales/crowdin/js-uk.yml @@ -24,3 +24,6 @@ uk: work_packages: properties: storyPoints: "Сторі-поінти" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-uz.yml b/modules/backlogs/config/locales/crowdin/js-uz.yml index 4e452dc2a88..1280d022946 100644 --- a/modules/backlogs/config/locales/crowdin/js-uz.yml +++ b/modules/backlogs/config/locales/crowdin/js-uz.yml @@ -24,3 +24,6 @@ uz: work_packages: properties: storyPoints: "Story Points" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-vi.yml b/modules/backlogs/config/locales/crowdin/js-vi.yml index 1cad19bcb64..a79e0fa45db 100644 --- a/modules/backlogs/config/locales/crowdin/js-vi.yml +++ b/modules/backlogs/config/locales/crowdin/js-vi.yml @@ -24,3 +24,6 @@ vi: work_packages: properties: storyPoints: "Điểm cốt truyện" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-zh-CN.yml b/modules/backlogs/config/locales/crowdin/js-zh-CN.yml index 269643e869c..4d23720f048 100644 --- a/modules/backlogs/config/locales/crowdin/js-zh-CN.yml +++ b/modules/backlogs/config/locales/crowdin/js-zh-CN.yml @@ -24,3 +24,6 @@ zh-CN: work_packages: properties: storyPoints: "故事点" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/js-zh-TW.yml b/modules/backlogs/config/locales/crowdin/js-zh-TW.yml index cde894e9d83..54e76923bed 100644 --- a/modules/backlogs/config/locales/crowdin/js-zh-TW.yml +++ b/modules/backlogs/config/locales/crowdin/js-zh-TW.yml @@ -24,3 +24,6 @@ zh-TW: work_packages: properties: storyPoints: "故事點數" + burndown: + day: "Day" + points: "Points" diff --git a/modules/backlogs/config/locales/crowdin/ka.yml b/modules/backlogs/config/locales/crowdin/ka.yml index 6ba3be361cd..9f6ee61e840 100644 --- a/modules/backlogs/config/locales/crowdin/ka.yml +++ b/modules/backlogs/config/locales/crowdin/ka.yml @@ -25,6 +25,8 @@ ka: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "მდებარეობა" story_points: "ისტორიული წერტილები" @@ -43,128 +45,97 @@ ka: attributes: task_type: "Task type" backlogs: - add_new_story: "ახალი ისტორია" any: "ნებისმიერი" - backlog_settings: "Backlogs settings" - burndown_graph: "გამოწვის გრაფიკი" - card_paper_size: "Paper size for card printing" - chart_options: "გრაფიკის მორგება" - close: "დახურვა" - column_width: "სვეტის სიგანე:" - date: "დღე" + column_width: "Column width" definition_of_done: "დასრულების აღწერა" - generating_chart: "გრაფიკის გენერაცია..." - hours: "საათი" impediment: "წინააღმდეგობა" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." work_package_is_closed: "Work package is done, when" label_is_done_status: "Status %{status_name} means done" - no_burndown_data: "No burndown data available. It is necessary to have the sprint start- and end dates set." - points: "წერტილები" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "Positions could not be rebuilt." positions_rebuilt_successfully: "Positions rebuilt successfully." - properties: "თვისებები" rebuild: "თავიდან აგება" rebuild_positions: "Rebuild positions" remaining_hours: "დარჩენილი სამუშაო" - remaining_hours_ideal: "Remaining work (ideal)" show_burndown_chart: "გამოწვის გრაფიკი" story: "ამბავი" - story_points: "ისტორიული წერტილები" - story_points_ideal: "Story Points (ideal)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "ამოცანა" task_color: "ამოცანის ფერი" unassigned: "მიუნიჭებელი" user_preference: header_backlogs: "Backlogs module" button_update_backlogs: "Update backlogs module" - x_more: "კიდევ %{count}..." - backlogs_active: "აქტიური" - backlogs_any: "ნებისმიერი" - backlogs_inactive: "Project shows no activity" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Points burn up/down" backlogs_product_backlog: "Product backlog" - backlogs_product_backlog_is_empty: "Product backlog is empty" - backlogs_product_backlog_unsized: "The top of the product backlog has unsized stories" - backlogs_sizing_inconsistent: "Story sizes vary against their estimates" - backlogs_sprint_notes_missing: "Closed sprints without retrospective/review notes" - backlogs_sprint_unestimated: "Closed or active sprints with unestimated stories" - backlogs_sprint_unsized: "Project has stories on active or recently closed sprints that were not sized" - backlogs_sprints: "სპრინტები" backlogs_story: "ამბავი" backlogs_story_type: "ამბის ტიპები" backlogs_task: "ამოცანა" backlogs_task_type: "ამოცანის ტიპი" - backlogs_velocity_missing: "No velocity could be calculated for this project" - backlogs_velocity_varies: "Velocity varies significantly over sprints" backlogs_wiki_template: "Template for sprint wiki page" - backlogs_empty_title: "No versions are defined to be used in backlogs" - backlogs_empty_action_text: "To get started with backlogs, create a new version and assign it to a backlogs column." - button_edit_wiki: "ვიკის გვერდის ჩასწორება" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "აღმოჩენილია შემდეგი შეცდომები::" - error_intro_singular: "The following error was encountered:" - error_outro: "Please correct the above errors before submitting again." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "იდეალური" - inclusion: "სიაში ჩასმული არაა" - label_back_to_project: "პროექტის გვერდზე დაბრუნება" - label_backlog: "შეუსრულებელი ამოცანა" label_backlogs: "შეუსრულებელი ამოცანები" label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "IDs of blocked work packages" - label_burndown: "გამოწვა" label_column_in_backlog: "Column in backlog" - label_hours: "საათი" - label_work_package_hierarchy: "Work package Hierarchy" - label_master_backlog: "ძირითადი ჩამორჩენა" - label_not_prioritized: "not prioritized" - label_points: "წერტილი" label_points_burn_down: "ქვემოთ" label_points_burn_up: "ზემოთ" - label_product_backlog: "product backlog" - label_select_all: "ყველას მონიშვნა" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "სპრინტის ჩამორჩენა" - label_sprint_cards: "ბარათების გატანა" label_sprint_impediments: "Sprint Impediments" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Velocity %{velocity}, based on %{sprints} sprints with an average %{days} days" - label_stories: "ამბები" - label_stories_tasks: "ამბები/ამოცანები" label_task_board: "ამოცანების დაფა" - label_version_setting: "ვერსიები" - label_version: 'ვერსია' - label_webcal: "Webcal ლენტა" - label_wiki: "ვიკი" permission_view_master_backlog: "View master backlog" permission_view_taskboards: "View taskboards" permission_select_done_statuses: "აირჩიეთ დასრულების სტატუსები" permission_update_sprints: "სპრინტების განახლება" - points_accepted: "points accepted" - points_committed: "points committed" - points_resolved: "points resolved" - points_to_accept: "points not accepted" - points_to_resolve: "points not resolved" project_module_backlogs: "შეუსრულებელი ამოცანები" - rb_label_copy_tasks: "Copy work packages" - rb_label_copy_tasks_all: "ყველა" - rb_label_copy_tasks_none: "არაფერი" - rb_label_copy_tasks_open: "გახსნა" - rb_label_link_to_original: "Include link to original story" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "remaining work" - required_burn_rate_hours: "required burn rate (hours)" - required_burn_rate_points: "required burn rate (points)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Column in backlog" version_settings_display_option_left: "მარცხნივ" version_settings_display_option_none: "არა" diff --git a/modules/backlogs/config/locales/crowdin/kk.yml b/modules/backlogs/config/locales/crowdin/kk.yml index 725556911ef..8acb224e27b 100644 --- a/modules/backlogs/config/locales/crowdin/kk.yml +++ b/modules/backlogs/config/locales/crowdin/kk.yml @@ -25,6 +25,8 @@ kk: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Position" story_points: "Story Points" @@ -43,128 +45,97 @@ kk: attributes: task_type: "Task type" backlogs: - add_new_story: "New Story" any: "any" - backlog_settings: "Backlogs settings" - burndown_graph: "Burndown Graph" - card_paper_size: "Paper size for card printing" - chart_options: "Chart options" - close: "Close" - column_width: "Column width:" - date: "Day" + column_width: "Column width" definition_of_done: "Definition of Done" - generating_chart: "Generating Graph..." - hours: "Hours" impediment: "Impediment" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." work_package_is_closed: "Work package is done, when" label_is_done_status: "Status %{status_name} means done" - no_burndown_data: "No burndown data available. It is necessary to have the sprint start- and end dates set." - points: "Points" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "Positions could not be rebuilt." positions_rebuilt_successfully: "Positions rebuilt successfully." - properties: "Properties" rebuild: "Rebuild" rebuild_positions: "Rebuild positions" remaining_hours: "Remaining work" - remaining_hours_ideal: "Remaining work (ideal)" show_burndown_chart: "Burndown Chart" story: "Story" - story_points: "Story Points" - story_points_ideal: "Story Points (ideal)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "Task" task_color: "Task color" unassigned: "Unassigned" user_preference: header_backlogs: "Backlogs module" button_update_backlogs: "Update backlogs module" - x_more: "%{count} more..." - backlogs_active: "active" - backlogs_any: "any" - backlogs_inactive: "Project shows no activity" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Points burn up/down" backlogs_product_backlog: "Product backlog" - backlogs_product_backlog_is_empty: "Product backlog is empty" - backlogs_product_backlog_unsized: "The top of the product backlog has unsized stories" - backlogs_sizing_inconsistent: "Story sizes vary against their estimates" - backlogs_sprint_notes_missing: "Closed sprints without retrospective/review notes" - backlogs_sprint_unestimated: "Closed or active sprints with unestimated stories" - backlogs_sprint_unsized: "Project has stories on active or recently closed sprints that were not sized" - backlogs_sprints: "Sprints" backlogs_story: "Story" backlogs_story_type: "Story types" backlogs_task: "Task" backlogs_task_type: "Task type" - backlogs_velocity_missing: "No velocity could be calculated for this project" - backlogs_velocity_varies: "Velocity varies significantly over sprints" backlogs_wiki_template: "Template for sprint wiki page" - backlogs_empty_title: "No versions are defined to be used in backlogs" - backlogs_empty_action_text: "To get started with backlogs, create a new version and assign it to a backlogs column." - button_edit_wiki: "Edit wiki page" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "The following errors were encountered:" - error_intro_singular: "The following error was encountered:" - error_outro: "Please correct the above errors before submitting again." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ideal" - inclusion: "is not included in the list" - label_back_to_project: "Back to project page" - label_backlog: "Backlog" label_backlogs: "Backlogs" label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "IDs of blocked work packages" - label_burndown: "Burndown" label_column_in_backlog: "Column in backlog" - label_hours: "hours" - label_work_package_hierarchy: "Work package Hierarchy" - label_master_backlog: "Master Backlog" - label_not_prioritized: "not prioritized" - label_points: "points" label_points_burn_down: "Down" label_points_burn_up: "Up" - label_product_backlog: "product backlog" - label_select_all: "Select all" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "sprint backlog" - label_sprint_cards: "Export cards" label_sprint_impediments: "Sprint Impediments" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Velocity %{velocity}, based on %{sprints} sprints with an average %{days} days" - label_stories: "Stories" - label_stories_tasks: "Stories/Tasks" label_task_board: "Task board" - label_version_setting: "Versions" - label_version: 'Version' - label_webcal: "Webcal Feed" - label_wiki: "Wiki" permission_view_master_backlog: "View master backlog" permission_view_taskboards: "View taskboards" permission_select_done_statuses: "Select done statuses" permission_update_sprints: "Update sprints" - points_accepted: "points accepted" - points_committed: "points committed" - points_resolved: "points resolved" - points_to_accept: "points not accepted" - points_to_resolve: "points not resolved" project_module_backlogs: "Backlogs" - rb_label_copy_tasks: "Copy work packages" - rb_label_copy_tasks_all: "All" - rb_label_copy_tasks_none: "None" - rb_label_copy_tasks_open: "Open" - rb_label_link_to_original: "Include link to original story" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "remaining work" - required_burn_rate_hours: "required burn rate (hours)" - required_burn_rate_points: "required burn rate (points)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Column in backlog" version_settings_display_option_left: "left" version_settings_display_option_none: "none" diff --git a/modules/backlogs/config/locales/crowdin/ko.yml b/modules/backlogs/config/locales/crowdin/ko.yml index 5e9561eef55..1377245e15c 100644 --- a/modules/backlogs/config/locales/crowdin/ko.yml +++ b/modules/backlogs/config/locales/crowdin/ko.yml @@ -25,6 +25,8 @@ ko: description: "이 모듈은 애자일 팀이 Scrum 프로젝트에서 OpenProject로 작업할 수 있도록 하는 기능을 추가합니다." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "위치" story_points: "스토리 포인트" @@ -43,128 +45,95 @@ ko: attributes: task_type: "작업 유형" backlogs: - add_new_story: "새로운 스토리" any: "모두" - backlog_settings: "백로그 설정" - burndown_graph: "번다운 그래프" - card_paper_size: "카드 인쇄용 용지 크기" - chart_options: "차트 옵션" - close: "닫기" - column_width: "열 너비:" - date: "일" + column_width: "Column width" definition_of_done: "완료 정의" - generating_chart: "그래프 생성 중..." - hours: "시간" impediment: "제한" label_versions_default_fold_state: "접힌 버전 표시" caption_versions_default_fold_state: "백로그를 볼 때 버전은 기본적으로 확장되지 않습니다. 각 버전을 수동으로 확장해야 합니다." work_package_is_closed: "다음 경우에 작업 패키지가 완료됩니다." label_is_done_status: "%{status_name} 상태는 완료를 의미합니다." - no_burndown_data: "번다운 데이터를 사용할 수 없습니다. 스프린트 시작 및 종료 날짜가 설정되어 있어야 합니다." - points: "포인트" + points_label: + other: "points" positions_could_not_be_rebuilt: "위치를 다시 빌드할 수 없습니다." positions_rebuilt_successfully: "위치가 성공적으로 다시 빌드되었습니다." - properties: "속성" rebuild: "다시 빌드" rebuild_positions: "위치 다시 빌드" remaining_hours: "남은 작업" - remaining_hours_ideal: "남은 작업(적합함)" show_burndown_chart: "번다운 차트" story: "스토리" - story_points: "스토리 포인트" - story_points_ideal: "스토리 포인트(적합함)" + story_points: + other: "%{count} story points" task: "작업" task_color: "작업 색상" unassigned: "할당되지 않음" user_preference: header_backlogs: "백로그 모듈" button_update_backlogs: "백로그 모듈 업데이트" - x_more: "%{count}개 이상..." - backlogs_active: "활성" - backlogs_any: "모두" - backlogs_inactive: "프로젝트가 작업을 표시하지 않습니다." + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "포인트 번 업/다운" backlogs_product_backlog: "제품 백로그" - backlogs_product_backlog_is_empty: "제품 백로그가 비어 있습니다." - backlogs_product_backlog_unsized: "제품 백로그의 맨 위에 크기가 지정되지 않은 스토리가 있습니다." - backlogs_sizing_inconsistent: "스토리 크기는 예상치에 대해 다릅니다." - backlogs_sprint_notes_missing: "닫힌 스프린트(회고/검토 메모 없음)" - backlogs_sprint_unestimated: "닫힌 스프린트 또는 활성 스프린트(추정되지 않은 스토리 있음)" - backlogs_sprint_unsized: "프로젝트에 활성 스프린트 또는 크기가 지정되지 않은 최근에 닫힌 스프린트의 스토리가 있습니다." - backlogs_sprints: "스프린트" backlogs_story: "스토리" backlogs_story_type: "스토리 유형" backlogs_task: "일감" backlogs_task_type: "작업 유형" - backlogs_velocity_missing: "이 프로젝트에 대한 속도를 계산할 수 없습니다." - backlogs_velocity_varies: "속도는 스프린트에서 상당히 다릅니다." backlogs_wiki_template: "스프린트 위키 페이지에 대한 템플릿" - backlogs_empty_title: "백로그에 사용할 버전이 정의되지 않았습니다." - backlogs_empty_action_text: "백로그를 시작하려면 새 버전을 만들고 백로그 열에 이 버전을 할당하세요." - button_edit_wiki: "위키 페이지 편집" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "- 스토리 유형도 될 수 없습니다" - error_intro_plural: "다음 오류가 발생했습니다." - error_intro_singular: "다음 오류가 발생했습니다." - error_outro: "다시 제출하기 전에 위의 오류를 수정하세요." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "적합함" - inclusion: "은(는) 목록에 포함되어 있지 않습니다." - label_back_to_project: "프로젝트 페이지로 돌아가기" - label_backlog: "백로그" label_backlogs: "백로그" label_backlogs_unconfigured: "백로그를 아직 구성하지 않았습니다. %{administration} > %{plugins}(으)로 이동한 다음 이 플러그인의 %{configure} 링크를 클릭하세요. 필드를 설정한 후 이 페이지로 돌아가서 해당 도구 사용을 시작하세요." label_blocks_ids: "차단된 작업 패키지의 ID" - label_burndown: "번다운" label_column_in_backlog: "백로그의 열" - label_hours: "시간" - label_work_package_hierarchy: "작업 패키지 계층 구조" - label_master_backlog: "마스터 백로그" - label_not_prioritized: "우선 순위 지정 안 됨" - label_points: "포인트" label_points_burn_down: "아래" label_points_burn_up: "위" - label_product_backlog: "제품 백로그" - label_select_all: "모두 선택" label_select_type: "유형 선택" label_select_types: "유형 선택" label_selected_type: "선택된 유형" label_selected_types: "선택된 유형" - label_sprint_backlog: "스프린트 백로그" - label_sprint_cards: "카드 내보내기" label_sprint_impediments: "스프린트 제한" - label_sprint_name: "스프린트 \"%{name}\"" - label_sprint_velocity: "속도 %{velocity}, 평균 %{days}일의 %{sprints} 스프린트 기준" - label_stories: "스토리" - label_stories_tasks: "스토리/작업" label_task_board: "작업 보드" - label_version_setting: "버전" - label_version: '버전' - label_webcal: "Webcal 피드" - label_wiki: "위키" permission_view_master_backlog: "마스터 백로그 보기" permission_view_taskboards: "작업 보드 보기" permission_select_done_statuses: "완료 상태 선택" permission_update_sprints: "스프린트 업데이트" - points_accepted: "포인트 수락됨" - points_committed: "포인트 커밋됨" - points_resolved: "포인트 확인됨" - points_to_accept: "포인트 수락 안 됨" - points_to_resolve: "포인트 확인 안 됨" project_module_backlogs: "백로그" - rb_label_copy_tasks: "작업 패키지 복사" - rb_label_copy_tasks_all: "모두" - rb_label_copy_tasks_none: "없음" - rb_label_copy_tasks_open: "열기" - rb_label_link_to_original: "원래 스토리의 링크 포함" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "남은 작업" - required_burn_rate_hours: "필요한 진행 속도(시간)" - required_burn_rate_points: "필요한 진행 속도(포인트)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "백로그의 열" version_settings_display_option_left: "왼쪽" version_settings_display_option_none: "없음" diff --git a/modules/backlogs/config/locales/crowdin/lt.yml b/modules/backlogs/config/locales/crowdin/lt.yml index 12a5be17656..0c7f8d9c46c 100644 --- a/modules/backlogs/config/locales/crowdin/lt.yml +++ b/modules/backlogs/config/locales/crowdin/lt.yml @@ -25,6 +25,8 @@ lt: description: "Šis modulis prideda funkcionalumą, leidžianti agile komandoms dirbti su OpenProject Scrum projektuose." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Vieta" story_points: "Istorijos taškai" @@ -43,128 +45,101 @@ lt: attributes: task_type: "Task type" backlogs: - add_new_story: "Nauja istorija" any: "bet koks" - backlog_settings: "Darbų sąrašo nustatymai" - burndown_graph: "Perdegimo Grafas" - card_paper_size: "Lapo dydis kortelių spausdinimui" - chart_options: "Diagramos parinktys" - close: "Uždaryti" - column_width: "Stulpelio plotis:" - date: "Diena" + column_width: "Column width" definition_of_done: "Pabaigimo apibrėžimas" - generating_chart: "Kuriamas grafas..." - hours: "Valandų" impediment: "Kliūtis" label_versions_default_fold_state: "Rodyti suskleistas versijas" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." work_package_is_closed: "Darbo paketas baigtas, kai" label_is_done_status: "Būsena %{status_name} reiškia atlikta" - no_burndown_data: "Nėra perdegimo duomenų. Būtina nustatyti sprinto pradžios ir pabaigos datas." - points: "Taškai" + points_label: + one: "point" + few: "points" + many: "points" + other: "points" positions_could_not_be_rebuilt: "Nepavyko perkurti pozicijų." positions_rebuilt_successfully: "Pozicijos sėkmingai perkurtos." - properties: "Ypatybės" rebuild: "Perkurti" rebuild_positions: "Perkurti pozicijas" remaining_hours: "Liko darbo" - remaining_hours_ideal: "Liko darbo (idealiai)" show_burndown_chart: "Perdegimo lentelė" story: "Istorija" - story_points: "Istorijos taškai" - story_points_ideal: "Istorijos taškai (idealiai)" + story_points: + one: "%{count} story point" + few: "%{count} story points" + many: "%{count} story points" + other: "%{count} story points" task: "Užduotis" task_color: "Užduoties spalva" unassigned: "Nepriskirta" user_preference: header_backlogs: "Backlogs module" button_update_backlogs: "Update backlogs module" - x_more: "dar %{count}..." - backlogs_active: "aktyvus" - backlogs_any: "bet koks" - backlogs_inactive: "Projekte nesimato aktyvumo" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Taškai dega aukštyn/žemyn" backlogs_product_backlog: "Produkto darbų sąrašas" - backlogs_product_backlog_is_empty: "Produkto darbų sąrašas yra tuščias" - backlogs_product_backlog_unsized: "Viršutinė produkto darbų sąrašo pozicija turi neįvertinų istorijų" - backlogs_sizing_inconsistent: "Istorijų dydis skiriasi nuo jų prognozių" - backlogs_sprint_notes_missing: "Uždaryti sprintai be retrospektyvinių/įvertinimo pastabų" - backlogs_sprint_unestimated: "Uždaryti ir aktyvūs sprintai su neįvertintomis istorijomis" - backlogs_sprint_unsized: "Projekto aktyvūs ir neseniai uždaryti sprintai turi istorijų, kurių dydis nėra įvertintas" - backlogs_sprints: "Sprintai" backlogs_story: "Istorija" backlogs_story_type: "Istorijų tipai" backlogs_task: "Užduotis" backlogs_task_type: "Užduoties tipas" - backlogs_velocity_missing: "Šiam projektui negalima paskaičiuoti greičio" - backlogs_velocity_varies: "Sprintų greitis žymiai skiriasi" backlogs_wiki_template: "Šablonas sprinto wiki puslapiui" - backlogs_empty_title: "Nėra versijų, skirtų naudoti darbų sąrašuose" - backlogs_empty_action_text: "Norėdami pradėti naudoti darbų sąrašus, sukurkite naują versiją ir priskirkite ją darbų sąrašo stulpeliui." - button_edit_wiki: "Redaguoti wiki puslapį" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "Rastos tokios klaidos:" - error_intro_singular: "Rasta tokia klaida:" - error_outro: "Prašome pataisyti aukščiau nurodytas klaidas prieš pateikiant dar kartą." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "idealus" - inclusion: "neįtraukta į sąrašą" - label_back_to_project: "Atgal į projekto puslapį" - label_backlog: "Darbų sąrašas" label_backlogs: "Darbų sąrašai" label_backlogs_unconfigured: "Jūs dar nesukonfigūravote Darbų sąrašų. Prašome eiti į %{administration} > %{plugins}, tada nuspausti ant %{configure} nuorodos šiam įskiepiui. Kai nustatysite laukus, grįžkite čia ir pradėkite naudoti instrumentą." label_blocks_ids: "Blokuotų darbų paketų ID reikšmės" - label_burndown: "Perdegimas" label_column_in_backlog: "Stulpelis darbų sąraše" - label_hours: "valandos (-a, -ų)" - label_work_package_hierarchy: "Darbų paketų hierarchija" - label_master_backlog: "Pagrindinis darbų sąrašas" - label_not_prioritized: "neprioritetizuota" - label_points: "taškai" label_points_burn_down: "Žemyn" label_points_burn_up: "Aukštyn" - label_product_backlog: "produkto darbų sąrašas" - label_select_all: "Pasirinkti viską" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "sprinto darbų sąrašas" - label_sprint_cards: "Eksportuoti korteles" label_sprint_impediments: "Sprinto trukdžiai" - label_sprint_name: "Sprintas „%{name}“" - label_sprint_velocity: "Greitis %{velocity}, pagal %{sprints} sprintus su vidutiniškai %{days} dienomis" - label_stories: "Istorijos" - label_stories_tasks: "Istorijos / Užduotys" label_task_board: "Užduočių lenta" - label_version_setting: "Versijos" - label_version: 'Versija' - label_webcal: "Webcal srautas" - label_wiki: "Wiki" permission_view_master_backlog: "Peržiūrėti pagrindinį darbų sąrašą" permission_view_taskboards: "Peržiūrėti užduočių lentas" permission_select_done_statuses: "Parinkite atliktas būsenas" permission_update_sprints: "Atnaujinti sprintus" - points_accepted: "taškai priimti" - points_committed: "taškai patvirtinti" - points_resolved: "taškai išspręsti" - points_to_accept: "taškai nepriimti" - points_to_resolve: "taškai neišspręsti" project_module_backlogs: "Darbų sąrašai" - rb_label_copy_tasks: "Nukopijuoti darbų paketus" - rb_label_copy_tasks_all: "Visi" - rb_label_copy_tasks_none: "Joks" - rb_label_copy_tasks_open: "Atidaryti" - rb_label_link_to_original: "Įtraukti nuorodą į originalią istoriją" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "liko darbo" - required_burn_rate_hours: "reikalingas degimo tempas (valandos)" - required_burn_rate_points: "reikalingas degimo tempas (taškai)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Stulpelis darbų sąraše" version_settings_display_option_left: "kairėn" version_settings_display_option_none: "joks" diff --git a/modules/backlogs/config/locales/crowdin/lv.yml b/modules/backlogs/config/locales/crowdin/lv.yml index d016b5af0e6..f43bbbecbd7 100644 --- a/modules/backlogs/config/locales/crowdin/lv.yml +++ b/modules/backlogs/config/locales/crowdin/lv.yml @@ -25,6 +25,8 @@ lv: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Pozīcija" story_points: "Stāsta punkti" @@ -43,128 +45,99 @@ lv: attributes: task_type: "Task type" backlogs: - add_new_story: "Jauns lietotājstāsts" any: "visi" - backlog_settings: "Produkta darbu krātuves iestatījumi" - burndown_graph: "Iedalījuma grafiks" - card_paper_size: "Papīra izmērs darbu pieteikumu kāršu drukāšanai" - chart_options: "Diagrammas opcijas" - close: "Aizvērt" - column_width: "Kolonnu platums:" - date: "Diena" + column_width: "Column width" definition_of_done: "Pabeigtības definīcija" - generating_chart: "Grafa ģenerēšana..." - hours: "Stundas" impediment: "Šķēršļi" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." work_package_is_closed: "Darba pieteikums ir bageits, kad " label_is_done_status: "Statuss %{status_name} nozīmē, ka darbis ir pabeigts" - no_burndown_data: "Nav pieejami dati par iedalījuma grafiku. Ir nepieciešams noteikt sprinta sākuma un beigu datumus." - points: "Punkti" + points_label: + zero: "points" + one: "point" + other: "points" positions_could_not_be_rebuilt: "Pozīcijas nevarēja atjaunot." positions_rebuilt_successfully: "Pozīcijas veiksmīgi atjaunotas." - properties: "Iestatījumi" rebuild: "Atjaunot" rebuild_positions: "Rebuild positions" remaining_hours: "Atlikušie darbi" - remaining_hours_ideal: "Atlikušie darbi (ideāls variants)" show_burndown_chart: "Iedalījuma diagramma" story: "Stāsts" - story_points: "Stāsta punkti" - story_points_ideal: "Stāsta punkti (ideāli)" + story_points: + zero: "%{count} story points" + one: "%{count} story point" + other: "%{count} story points" task: "Uzdevums" task_color: "Uzdevumakrāsa" unassigned: "Nepiešķirts" user_preference: header_backlogs: "Backlogs module" button_update_backlogs: "Update backlogs module" - x_more: "%{count} vairāk..." - backlogs_active: "Aktīvs" - backlogs_any: "jebkurš" - backlogs_inactive: "Projektā nav uzrādāmu darbību" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Points burn up/down" backlogs_product_backlog: "Produkta darbu krātuve" - backlogs_product_backlog_is_empty: "Produkta darbu krātuve ir tukša" - backlogs_product_backlog_unsized: "Produkta darbu krātuves saraksta augšgalā ir nenovērtētie darbu pieteikumi" - backlogs_sizing_inconsistent: "Darba novērtējus atšķiras no tā sākotnēji novērtētā " - backlogs_sprint_notes_missing: "Slēgtie sprinti bez retrospekcijas/pārskatīšanas piezīmēm" - backlogs_sprint_unestimated: "Slēgti vai aktīvi sprinti ar nenovērtētiem stāstiem" - backlogs_sprint_unsized: "Projektā ir stāsti par aktīviem vai nesen pabeigtiem sprintiem, kuru izmērs nav noteikts." - backlogs_sprints: "Sprinti" backlogs_story: "Stāsts" backlogs_story_type: "Lietotājstāstu tipi" backlogs_task: "Uzdevums" backlogs_task_type: "Pieteikumu tipi" - backlogs_velocity_missing: "No velocity could be calculated for this project" - backlogs_velocity_varies: "Velocity varies significantly over sprints" backlogs_wiki_template: "Template for sprint wiki page" - backlogs_empty_title: "No versions are defined to be used in backlogs" - backlogs_empty_action_text: "To get started with backlogs, create a new version and assign it to a backlogs column." - button_edit_wiki: "Rediģēt wiki lapu" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "The following errors were encountered:" - error_intro_singular: "The following error was encountered:" - error_outro: "Please correct the above errors before submitting again." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ideal" - inclusion: "nav iekļauts sarakstā" - label_back_to_project: "Atpakaļ uz projekta lapu" - label_backlog: "Neizpildīti darbi" label_backlogs: "Darbu krātuve" label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "IDs of blocked work packages" - label_burndown: "Burndown" label_column_in_backlog: "Atlikušo darbu backlog" - label_hours: "stundas" - label_work_package_hierarchy: "Pieteikumu hierarhija" - label_master_backlog: "Viss backlog" - label_not_prioritized: "nav noteikta prioritāte" - label_points: "punkti" label_points_burn_down: "Lejup" label_points_burn_up: "Augšup" - label_product_backlog: "produkta backlog" - label_select_all: "Atzīmēt visu" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "sprinta neizpildīto uzdevumu saraksts" - label_sprint_cards: "Eksporta kartītes" label_sprint_impediments: "Sprinta šķēršļi" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Velocity %{velocity}, based on %{sprints} sprints with an average %{days} days" - label_stories: "Lietotājstāsti" - label_stories_tasks: "Lietotājstāsti/Pieteikumi" label_task_board: "Pieteikumu tāfele" - label_version_setting: "Versijas" - label_version: 'Versija' - label_webcal: "Webcal Feed" - label_wiki: "Wiki" permission_view_master_backlog: "Skatīt visus nepabeigtos darbus" permission_view_taskboards: "Apskatīt uzdevumu dēļus" permission_select_done_statuses: "Izvēlieties pabeigtības statusu" permission_update_sprints: "Atjaunināt sprintus" - points_accepted: "Akceptētais novērtējums" - points_committed: "Piešķirtais novērtējums" - points_resolved: "Atrisinātais novērtējums" - points_to_accept: "Neakceptētais novērtējums" - points_to_resolve: "Neatrisinātais novērtējums" project_module_backlogs: "Darbu krātuve" - rb_label_copy_tasks: "Kopēt darbu pieteikumus" - rb_label_copy_tasks_all: "Visi" - rb_label_copy_tasks_none: "Neviens" - rb_label_copy_tasks_open: "Atvērts" - rb_label_link_to_original: "Iekļaut saiti uz sākotnējo stāstu" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "atlikušais darbs" - required_burn_rate_hours: "required burn rate (hours)" - required_burn_rate_points: "required burn rate (points)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Atlikušo darbu backlog" version_settings_display_option_left: "pa kreisi" version_settings_display_option_none: "Neviens" diff --git a/modules/backlogs/config/locales/crowdin/mn.yml b/modules/backlogs/config/locales/crowdin/mn.yml index 6896179d4af..411d5f0b2a4 100644 --- a/modules/backlogs/config/locales/crowdin/mn.yml +++ b/modules/backlogs/config/locales/crowdin/mn.yml @@ -25,6 +25,8 @@ mn: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Байршил" story_points: "Гүйцэтгэлийн оноо" @@ -43,128 +45,97 @@ mn: attributes: task_type: "Task type" backlogs: - add_new_story: "Шинэ Түүх" any: "ямар ч" - backlog_settings: "Backlogs settings" - burndown_graph: "Burndown Graph" - card_paper_size: "Paper size for card printing" - chart_options: "Chart options" - close: "Хаах" - column_width: "Column width:" - date: "Өдөр" + column_width: "Column width" definition_of_done: "Definition of Done" - generating_chart: "Generating Graph..." - hours: "Цаг" impediment: "Impediment" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." work_package_is_closed: "Work package is done, when" label_is_done_status: "Төлөв %{status_name} дууссан гэсэн үг" - no_burndown_data: "No burndown data available. It is necessary to have the sprint start- and end dates set." - points: "Оноо" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "Positions could not be rebuilt." positions_rebuilt_successfully: "Positions rebuilt successfully." - properties: "Properties" rebuild: "Rebuild" rebuild_positions: "Rebuild positions" remaining_hours: "Remaining work" - remaining_hours_ideal: "Remaining work (ideal)" show_burndown_chart: "Burndown Chart" story: "Story" - story_points: "Гүйцэтгэлийн оноо" - story_points_ideal: "Story Points (ideal)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "Даалгавар" task_color: "Даалгаврын өнгө" unassigned: "Unassigned" user_preference: header_backlogs: "Backlogs module" button_update_backlogs: "Update backlogs module" - x_more: "%{count} more..." - backlogs_active: "идэвхтэй" - backlogs_any: "ямар ч" - backlogs_inactive: "Project shows no activity" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Points burn up/down" backlogs_product_backlog: "Product backlog" - backlogs_product_backlog_is_empty: "Product backlog is empty" - backlogs_product_backlog_unsized: "The top of the product backlog has unsized stories" - backlogs_sizing_inconsistent: "Story sizes vary against their estimates" - backlogs_sprint_notes_missing: "Closed sprints without retrospective/review notes" - backlogs_sprint_unestimated: "Closed or active sprints with unestimated stories" - backlogs_sprint_unsized: "Project has stories on active or recently closed sprints that were not sized" - backlogs_sprints: "Sprints" backlogs_story: "Story" backlogs_story_type: "Story types" backlogs_task: "Даалгавар" backlogs_task_type: "Task type" - backlogs_velocity_missing: "No velocity could be calculated for this project" - backlogs_velocity_varies: "Velocity varies significantly over sprints" backlogs_wiki_template: "Template for sprint wiki page" - backlogs_empty_title: "No versions are defined to be used in backlogs" - backlogs_empty_action_text: "To get started with backlogs, create a new version and assign it to a backlogs column." - button_edit_wiki: "Edit wiki page" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "The following errors were encountered:" - error_intro_singular: "The following error was encountered:" - error_outro: "Please correct the above errors before submitting again." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "хамгийн тохиромжтой" - inclusion: "is not included in the list" - label_back_to_project: "Back to project page" - label_backlog: "Backlog" label_backlogs: "Backlogs" label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "IDs of blocked work packages" - label_burndown: "Burndown" label_column_in_backlog: "Column in backlog" - label_hours: "hours" - label_work_package_hierarchy: "Work package Hierarchy" - label_master_backlog: "Master Backlog" - label_not_prioritized: "not prioritized" - label_points: "points" label_points_burn_down: "Down" label_points_burn_up: "Up" - label_product_backlog: "product backlog" - label_select_all: "Select all" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "sprint backlog" - label_sprint_cards: "Export cards" label_sprint_impediments: "Sprint Impediments" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Velocity %{velocity}, based on %{sprints} sprints with an average %{days} days" - label_stories: "Stories" - label_stories_tasks: "Stories/Tasks" label_task_board: "Task board" - label_version_setting: "Versions" - label_version: 'Хувилбар' - label_webcal: "Webcal Feed" - label_wiki: "Wiki" permission_view_master_backlog: "View master backlog" permission_view_taskboards: "View taskboards" permission_select_done_statuses: "Select done statuses" permission_update_sprints: "Update sprints" - points_accepted: "points accepted" - points_committed: "points committed" - points_resolved: "points resolved" - points_to_accept: "points not accepted" - points_to_resolve: "points not resolved" project_module_backlogs: "Backlogs" - rb_label_copy_tasks: "Copy work packages" - rb_label_copy_tasks_all: "All" - rb_label_copy_tasks_none: "None" - rb_label_copy_tasks_open: "Open" - rb_label_link_to_original: "Include link to original story" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "remaining work" - required_burn_rate_hours: "required burn rate (hours)" - required_burn_rate_points: "required burn rate (points)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Column in backlog" version_settings_display_option_left: "left" version_settings_display_option_none: "none" diff --git a/modules/backlogs/config/locales/crowdin/ms.yml b/modules/backlogs/config/locales/crowdin/ms.yml index ecf1b25d510..b96c09c8df9 100644 --- a/modules/backlogs/config/locales/crowdin/ms.yml +++ b/modules/backlogs/config/locales/crowdin/ms.yml @@ -25,6 +25,8 @@ ms: description: "Modul ini menambahkan fitur-fitur yang membolehkan kumpulan-kumpulan yang tangkas untuk bekerja menggunakan OpenProject di projek Scrum." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Kedudukan" story_points: "Titik Cerita" @@ -43,128 +45,95 @@ ms: attributes: task_type: "Task type" backlogs: - add_new_story: "Cerita Baharu" any: "sebarang" - backlog_settings: "Tetapan tunggakan" - burndown_graph: "Graf Burndown" - card_paper_size: "Saiz kertas untuk pencetakan kad" - chart_options: "Pilihan carta" - close: "Tutup" - column_width: "Lebar kolum:" - date: "Hari" + column_width: "Column width" definition_of_done: "Definisi Selesai" - generating_chart: "Sedang Menjana Graf..." - hours: "Jam" impediment: "Halangan" label_versions_default_fold_state: "Paparkan versi dilipat" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." work_package_is_closed: "Pakej kerja selesai apabila" label_is_done_status: "Status %{status_name} bermaksud selesai" - no_burndown_data: "Tiada data burndown tersedia. Ia diperlukan untuk menentukan tarikh mula dan tarikh akhir pecutan." - points: "Mata" + points_label: + other: "points" positions_could_not_be_rebuilt: "Kedudukan tidak boleh dibina semula." positions_rebuilt_successfully: "Kedudukan berjaya dibina semula." - properties: "Ciri-ciri" rebuild: "Bina semula" rebuild_positions: "Bina semula kedudukan" remaining_hours: "Kerja yang berbaki" - remaining_hours_ideal: "Kerja yang berbaki (ideal)" show_burndown_chart: "Carta Burndown" story: "Cerita" - story_points: "Titik Cerita" - story_points_ideal: "Titik Cerita (ideal)" + story_points: + other: "%{count} story points" task: "Tugasan" task_color: "Warna tugasan" unassigned: "Belum Ditetapkan" user_preference: header_backlogs: "Backlogs module" button_update_backlogs: "Update backlogs module" - x_more: "%{count} lagi..." - backlogs_active: "aktif" - backlogs_any: "sebarang" - backlogs_inactive: "Projek menunjukkan tiada sebarang aktiviti" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Mata untuk burn up/down" backlogs_product_backlog: "Tunggakan produk" - backlogs_product_backlog_is_empty: "Tunggakan produk kosong" - backlogs_product_backlog_unsized: "Bahagian atas tunggakan produk mempunyai cerita tanpa saiz" - backlogs_sizing_inconsistent: "Saiz cerita berbeza mengikut anggaran" - backlogs_sprint_notes_missing: "Pecutan ditutup tanpa nota restrospektif/semakan" - backlogs_sprint_unestimated: "Pecutan aktif atau ditutup bersama cerita yang tiada anggaran" - backlogs_sprint_unsized: "Projek mempunyai cerita-cerita berkenaan pecutan aktif atau yang baru ditutup yang tidak bersaiz" - backlogs_sprints: "Pecutan" backlogs_story: "Cerita" backlogs_story_type: "Jenis cerita" backlogs_task: "Tugasan" backlogs_task_type: "Jenis tugasan" - backlogs_velocity_missing: "Tiada kelajuan yang boleh dikira untuk projek ini" - backlogs_velocity_varies: "Kelajuan berubah signifikan dengan pecutan" backlogs_wiki_template: "Templat untuk pecutan halaman wiki" - backlogs_empty_title: "Tiada versi ditetapkan untuk digunakan dalam tunggakan" - backlogs_empty_action_text: "Untuk bermula dengan tunggakan, cipta satu versi baru dan tetapkan ia ke kolum tunggakan." - button_edit_wiki: "Edit halaman wiki" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "Ralat-ralat berikut telah ditemui:" - error_intro_singular: "Ralat berikut telah ditemui:" - error_outro: "Sila betulkan ralat-ralat di atas sebelum menghantar semula." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ideal" - inclusion: "tidak disertakan dalam senarai" - label_back_to_project: "Kembali ke laman projek" - label_backlog: "Tunggakan" label_backlogs: "Tunggakan" label_backlogs_unconfigured: "Anda masih belum mengkonfigurasi tunggakan. Sila pergi ke %{administration} > %{plugins}, kemudian klik pautan %{configure} untuk plugin ini. Setelah anda menetapkan ruangan, kembali ke halaman ini untuk mula menggunakan alat ini." label_blocks_ids: "ID pakej kerja yang disekat" - label_burndown: "Burndown" label_column_in_backlog: "Kolum dalam tunggakan" - label_hours: "jam" - label_work_package_hierarchy: "Hierarki pakej kerja" - label_master_backlog: "Tunggakan Utama" - label_not_prioritized: "tidak diutamakan" - label_points: "mata" label_points_burn_down: "Bawah" label_points_burn_up: "Atas" - label_product_backlog: "produk tunggakan" - label_select_all: "Pilih semua" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "tunggakan pecutan " - label_sprint_cards: "Eksport kad" label_sprint_impediments: "Halangan Pecutan" - label_sprint_name: "Pecutan \"%{name}\"" - label_sprint_velocity: "Kelajuan %{velocity}, berdasarkan %{sprints} pecutan dengan purata %{days} hari" - label_stories: "Cerita-cerita" - label_stories_tasks: "Cerita/Tugasan" label_task_board: "Papan tugasan" - label_version_setting: "Versi-versi" - label_version: 'Versi' - label_webcal: "Webcal Feed" - label_wiki: "Wiki" permission_view_master_backlog: "Paparkan tunggakan utama" permission_view_taskboards: "Lihat papan tugasan" permission_select_done_statuses: "Pilih status selesai" permission_update_sprints: "Kemas kini pecutan" - points_accepted: "mata diterima" - points_committed: "mata yang komited" - points_resolved: "mata yang diselesaikan" - points_to_accept: "mata yang tidak diterima" - points_to_resolve: "mata tidak diselesaikan" project_module_backlogs: "Tunggakan" - rb_label_copy_tasks: "Salin pakej kerja" - rb_label_copy_tasks_all: "Semua" - rb_label_copy_tasks_none: "Tiada" - rb_label_copy_tasks_open: "Buka" - rb_label_link_to_original: "Sertakan pautan ke cerita asal" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "kerja yang berbaki" - required_burn_rate_hours: "kadar burn yang diperlukan (jam)" - required_burn_rate_points: "kadar burn yang diperlukan (mata)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Kolum dalam tunggakan" version_settings_display_option_left: "kiri" version_settings_display_option_none: "tiada" diff --git a/modules/backlogs/config/locales/crowdin/ne.yml b/modules/backlogs/config/locales/crowdin/ne.yml index abff5076685..05c787a3b54 100644 --- a/modules/backlogs/config/locales/crowdin/ne.yml +++ b/modules/backlogs/config/locales/crowdin/ne.yml @@ -25,6 +25,8 @@ ne: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "स्थान" story_points: "Story Points" @@ -43,128 +45,97 @@ ne: attributes: task_type: "Task type" backlogs: - add_new_story: "New Story" any: "any" - backlog_settings: "ब्याकलोग सेटिंगहरु" - burndown_graph: "Burndown Graph" - card_paper_size: "Paper size for card printing" - chart_options: "Chart options" - close: "Close" - column_width: "Column width:" - date: "Day" + column_width: "Column width" definition_of_done: "Definition of Done" - generating_chart: "Generating Graph..." - hours: "Hours" impediment: "Impediment" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." work_package_is_closed: "Work package is done, when" label_is_done_status: "Status %{status_name} means done" - no_burndown_data: "No burndown data available. It is necessary to have the sprint start- and end dates set." - points: "Points" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "Positions could not be rebuilt." positions_rebuilt_successfully: "Positions rebuilt successfully." - properties: "Properties" rebuild: "Rebuild" rebuild_positions: "Rebuild positions" remaining_hours: "Remaining work" - remaining_hours_ideal: "Remaining work (ideal)" show_burndown_chart: "Burndown Chart" story: "Story" - story_points: "Story Points" - story_points_ideal: "Story Points (ideal)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "Task" task_color: "Task color" unassigned: "Unassigned" user_preference: header_backlogs: "Backlogs module" button_update_backlogs: "Update backlogs module" - x_more: "%{count} more..." - backlogs_active: "active" - backlogs_any: "any" - backlogs_inactive: "Project shows no activity" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Points burn up/down" backlogs_product_backlog: "Product backlog" - backlogs_product_backlog_is_empty: "Product backlog is empty" - backlogs_product_backlog_unsized: "The top of the product backlog has unsized stories" - backlogs_sizing_inconsistent: "Story sizes vary against their estimates" - backlogs_sprint_notes_missing: "Closed sprints without retrospective/review notes" - backlogs_sprint_unestimated: "Closed or active sprints with unestimated stories" - backlogs_sprint_unsized: "Project has stories on active or recently closed sprints that were not sized" - backlogs_sprints: "Sprints" backlogs_story: "Story" backlogs_story_type: "Story types" backlogs_task: "Task" backlogs_task_type: "Task type" - backlogs_velocity_missing: "No velocity could be calculated for this project" - backlogs_velocity_varies: "Velocity varies significantly over sprints" backlogs_wiki_template: "Template for sprint wiki page" - backlogs_empty_title: "No versions are defined to be used in backlogs" - backlogs_empty_action_text: "To get started with backlogs, create a new version and assign it to a backlogs column." - button_edit_wiki: "Edit wiki page" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "The following errors were encountered:" - error_intro_singular: "The following error was encountered:" - error_outro: "Please correct the above errors before submitting again." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ideal" - inclusion: "is not included in the list" - label_back_to_project: "Back to project page" - label_backlog: "Backlog" label_backlogs: "Backlogs" label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "IDs of blocked work packages" - label_burndown: "Burndown" label_column_in_backlog: "Column in backlog" - label_hours: "hours" - label_work_package_hierarchy: "Work package Hierarchy" - label_master_backlog: "Master Backlog" - label_not_prioritized: "not prioritized" - label_points: "points" label_points_burn_down: "Down" label_points_burn_up: "Up" - label_product_backlog: "product backlog" - label_select_all: "Select all" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "sprint backlog" - label_sprint_cards: "Export cards" label_sprint_impediments: "Sprint Impediments" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Velocity %{velocity}, based on %{sprints} sprints with an average %{days} days" - label_stories: "Stories" - label_stories_tasks: "Stories/Tasks" label_task_board: "Task board" - label_version_setting: "Versions" - label_version: 'Version' - label_webcal: "Webcal Feed" - label_wiki: "Wiki" permission_view_master_backlog: "View master backlog" permission_view_taskboards: "View taskboards" permission_select_done_statuses: "Select done statuses" permission_update_sprints: "Update sprints" - points_accepted: "points accepted" - points_committed: "points committed" - points_resolved: "points resolved" - points_to_accept: "points not accepted" - points_to_resolve: "points not resolved" project_module_backlogs: "Backlogs" - rb_label_copy_tasks: "Copy work packages" - rb_label_copy_tasks_all: "All" - rb_label_copy_tasks_none: "None" - rb_label_copy_tasks_open: "Open" - rb_label_link_to_original: "Include link to original story" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "remaining work" - required_burn_rate_hours: "required burn rate (hours)" - required_burn_rate_points: "required burn rate (points)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Column in backlog" version_settings_display_option_left: "left" version_settings_display_option_none: "none" diff --git a/modules/backlogs/config/locales/crowdin/nl.yml b/modules/backlogs/config/locales/crowdin/nl.yml index 2b26c992425..a8a437bd8b6 100644 --- a/modules/backlogs/config/locales/crowdin/nl.yml +++ b/modules/backlogs/config/locales/crowdin/nl.yml @@ -25,6 +25,8 @@ nl: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Positie" story_points: "Verhaal punten" @@ -43,128 +45,97 @@ nl: attributes: task_type: "Task type" backlogs: - add_new_story: "Nieuwe Story" any: "elke" - backlog_settings: "Instellingen backlogs" - burndown_graph: "Burndown grafiek" - card_paper_size: "Papierformaat voor het afdrukken van de kaart" - chart_options: "Grafiekopties" - close: "Sluiten" - column_width: "Kolombreedte:" - date: "Dag" + column_width: "Column width" definition_of_done: "Definitie van Klaar" - generating_chart: "Grafiekt aan het genereren..." - hours: "Uren" impediment: "Belemmering" label_versions_default_fold_state: "Laat versies samengevouwen zien" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." work_package_is_closed: "Het werkpakket is klaar wanneer" label_is_done_status: "Status van %{status_name} betekent gedaan" - no_burndown_data: "Geen burndown-gegevens beschikbaar. Start- en einddata van de sprints moeten worden gedefinieerd." - points: "Punten" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "Posities kon niet worden herbouwd." positions_rebuilt_successfully: "Posities met succes herbouwd." - properties: "Eigenschappen" rebuild: "Opnieuw opbouwen" rebuild_positions: "Posities van opnieuw opbouwen" remaining_hours: "Resterend werk" - remaining_hours_ideal: "Resterend werk (ideaal)" show_burndown_chart: "Verbrandingstabel" story: "Verhaal" - story_points: "Verhaal punten" - story_points_ideal: "Verhaal punten (ideaal)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "Taak" task_color: "Taakkleur" unassigned: "Niet-toegewezen" user_preference: header_backlogs: "Backlogs module" button_update_backlogs: "Update backlogs module" - x_more: "%{count} meer..." - backlogs_active: "actief" - backlogs_any: "elke" - backlogs_inactive: "Het project toont geen activiteit" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Punten gaan omhoog / omlaag" backlogs_product_backlog: "Product backlog" - backlogs_product_backlog_is_empty: "Product backlog is leeg" - backlogs_product_backlog_unsized: "De bovenkant van de product backlog heeft verhalen zonder lengte" - backlogs_sizing_inconsistent: "Verhaalgroottes variëren van hun schattingen" - backlogs_sprint_notes_missing: "Gesloten sprints zonder retrospectieve / beoordelingsnotities" - backlogs_sprint_unestimated: "Gesloten of actieve sprints met onvoorspelbare verhalen" - backlogs_sprint_unsized: "Het project bevat verhalen over actieve of recentelijk gesloten sprints waarvan de grootte niet is opgegeven" - backlogs_sprints: "Sprints" backlogs_story: "Verhaal" backlogs_story_type: "Verhaaltype" backlogs_task: "Taak" backlogs_task_type: "Taaktype" - backlogs_velocity_missing: "Voor dit project kon geen snelheid berekenen" - backlogs_velocity_varies: "Snelheid varieert aanmerkelijk ten opzichte van sprints" backlogs_wiki_template: "Sjabloon voor sprint wikipagina" - backlogs_empty_title: "Er zijn geen versies gedefinieerd om te worden gebruikt in de backlogs" - backlogs_empty_action_text: "Om te beginnen met backlogs, maak een nieuwe versie en ken deze toe aan een backlog kolom." - button_edit_wiki: "Wikipagina bewerken" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "De volgende fouten zijn opgetreden:" - error_intro_singular: "De volgende fout is opgetreden:" - error_outro: "Los de bovenstaande fouten op voordat u opnieuw verzendt." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ideaal" - inclusion: "is niet opgenomen in de lijst" - label_back_to_project: "Terug naar projectpagina" - label_backlog: "Backlog" label_backlogs: "Backlogs" label_backlogs_unconfigured: "Je hebt Backlogs nog niet geconfigureerd. Ga naar %{administration} >%{plugins}, en klik op de %{configure} voor deze plug-in. Kom hier terug nadat u de velden hebt geconfigureerd." label_blocks_ids: "ID's van geblokkeerde werkpakketten" - label_burndown: "Burndown" label_column_in_backlog: "Kolom in achterstand" - label_hours: "uren" - label_work_package_hierarchy: "Werkpakket Hiërarchie" - label_master_backlog: "Master Backlog" - label_not_prioritized: "niet geprioriteerd" - label_points: "punten" label_points_burn_down: "Omlaag" label_points_burn_up: "Omhoog" - label_product_backlog: "product backlog" - label_select_all: "Alles selecteren" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "sprint backlog" - label_sprint_cards: "Exporteer de kaarten" label_sprint_impediments: "Sprint Obstakels" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Snelheid %{velocity}, op basis van %{sprints} sprints met een gemiddelde van %{days} dagen" - label_stories: "Verhalen" - label_stories_tasks: "Verhalen/taken" label_task_board: "Taakbord" - label_version_setting: "Versies" - label_version: 'Versie' - label_webcal: "Webcal Feed" - label_wiki: "Wiki" permission_view_master_backlog: "Toon Máster Backlog" permission_view_taskboards: "Bekijk taakborden" permission_select_done_statuses: "Selecteer voltooide statussen" permission_update_sprints: "Sprints bijwerken" - points_accepted: "punten aanvaard" - points_committed: "punten begaan" - points_resolved: "punten opgelost" - points_to_accept: "punten niet aanvaard" - points_to_resolve: "punten niet opgelost" project_module_backlogs: "Backlogs" - rb_label_copy_tasks: "Werkpakketten kopiëren" - rb_label_copy_tasks_all: "Alle" - rb_label_copy_tasks_none: "Geen" - rb_label_copy_tasks_open: "Open" - rb_label_link_to_original: "Link toevoegen aan het originele verhaal" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "Resterend werk" - required_burn_rate_hours: "vereiste burn rate (uren)" - required_burn_rate_points: "vereiste burn rate (punten)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Kolom in backlog" version_settings_display_option_left: "links" version_settings_display_option_none: "geen" diff --git a/modules/backlogs/config/locales/crowdin/no.yml b/modules/backlogs/config/locales/crowdin/no.yml index 7df49f6b21a..b978a424d7a 100644 --- a/modules/backlogs/config/locales/crowdin/no.yml +++ b/modules/backlogs/config/locales/crowdin/no.yml @@ -25,6 +25,8 @@ description: "Denne modulen legger til funksjoner som setter dynamiske team i stand til å arbeide med OpenProject i Scrum prosjekter." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Plassering" story_points: "Historiepoeng" @@ -43,128 +45,97 @@ attributes: task_type: "Task type" backlogs: - add_new_story: "Ny historie" any: "hvilken som helst" - backlog_settings: "Innstillinger for forsinkelser" - burndown_graph: "Burndown-graf" - card_paper_size: "Papirstørrelse for kortutskrift" - chart_options: "Diagramalternativer" - close: "Lukk" - column_width: "Kolonnebredde:" - date: "Dag" + column_width: "Column width" definition_of_done: "Definisjon av ferdig" - generating_chart: "Genererer graf..." - hours: "Timer" impediment: "Hinder" label_versions_default_fold_state: "Vis versjoner kollapset" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." work_package_is_closed: "Arbeidspakke er ferdig, når" label_is_done_status: "Status %{status_name} betyr fullført" - no_burndown_data: "Ingen burndown-data tilgjengelig. Det er nødvendig å ha etappens start- og sluttdatoer satt." - points: "Poeng" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "Posisjoner kunne ikke gjenoppbygges." positions_rebuilt_successfully: "Vellykket gjenoppbygging av posisjoner." - properties: "Egenskaper" rebuild: "Gjenoppbygg" rebuild_positions: "Gjenoppbygg posisjoner" remaining_hours: "Gjenstående arbeid" - remaining_hours_ideal: "Gjenværende arbeid (ideell)" show_burndown_chart: "Burndown-graf" story: "Historie" - story_points: "Historiepoeng" - story_points_ideal: "Historiepoeng (ideell)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "Oppgave" task_color: "Farge på oppgave" unassigned: "Ikke tildelt" user_preference: header_backlogs: "Backlogs module" button_update_backlogs: "Update backlogs module" - x_more: "%{count} mer..." - backlogs_active: "aktiv" - backlogs_any: "hvilken som helst" - backlogs_inactive: "Prosjekter viser ingen aktivitet" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Poeng brenner opp/ned" backlogs_product_backlog: "Produktforsinkelser" - backlogs_product_backlog_is_empty: "Produktforsinkelser er tom" - backlogs_product_backlog_unsized: "Toppen av produktforsinkelser har historier uten størrelser" - backlogs_sizing_inconsistent: "Historiestørrelsene varierer fra estimater" - backlogs_sprint_notes_missing: "Stengte etapper uten tilbakeskuende/gjennomgangsnotater" - backlogs_sprint_unestimated: "Lukkede eller aktive etapper med uestimerte historier" - backlogs_sprint_unsized: "Prosjektet har historier i aktive eller nylig lukkede etapper uten størrelse" - backlogs_sprints: "Etapper" backlogs_story: "Historie" backlogs_story_type: "Historietyper" backlogs_task: "Oppgave" backlogs_task_type: "Oppgavetype" - backlogs_velocity_missing: "Ingen fremdrift kan beregnes for dette prosjektet" - backlogs_velocity_varies: "Fremdriften varierer betydelig over etapper" backlogs_wiki_template: "Mal for etappe wiki-side" - backlogs_empty_title: "Ingen versjoner er definert til å brukes i forsinkelser" - backlogs_empty_action_text: "For å komme i gang med forsinkelser, lag en ny versjon og legg den til i en forsinkelse-kolonne" - button_edit_wiki: "Rediger Wiki-side" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "Møtte på følgende feil:" - error_intro_singular: "Møtte på følgende feil:" - error_outro: "Venligst rett feilene over før du sender inn igjen." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "Ideell" - inclusion: "er ikke inkludert i listen" - label_back_to_project: "Tilbake til projektsiden" - label_backlog: "Backlog" label_backlogs: "Forsinkelser" label_backlogs_unconfigured: "Du har ikke konfigurert Forsinkelser enda. Gå til %{administration} > %{plugins}og klikk deretter på %{configure} lenken for denne utvidelsen. Når du har angitt felter, går du tilbake til denne siden for å begynne å bruke verktøyet." label_blocks_ids: "ID'er for blokkerte arbeidspakker" - label_burndown: "Gjenstående" label_column_in_backlog: "Kolonne i forsinkelse" - label_hours: " timer" - label_work_package_hierarchy: "Hierarki for arbeidspakker" - label_master_backlog: "Master Forsinkelse" - label_not_prioritized: "ikke prioritert" - label_points: "punkter" label_points_burn_down: "Ned" label_points_burn_up: "Opp" - label_product_backlog: "produkt-forsinkelse" - label_select_all: "Velg alle" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "sprint backlog" - label_sprint_cards: "Eksporter kort" label_sprint_impediments: "Hindring i etappe" - label_sprint_name: "Etappe \"%{name}\"" - label_sprint_velocity: "Fremdrift %{velocity}, basert på %{sprints} etapper med gjennomsnittlig %{days} dager" - label_stories: "Historier" - label_stories_tasks: "Historier/Oppgaver" label_task_board: "Oppgavetavle" - label_version_setting: "Versjoner" - label_version: 'Versjon' - label_webcal: "Webcal Feed" - label_wiki: "Wiki" permission_view_master_backlog: "Vis master forsinkelse" permission_view_taskboards: "Vis oppgavetavler" permission_select_done_statuses: "Velg ferdige statuser" permission_update_sprints: "Oppdater etapper" - points_accepted: "poeng akseptert" - points_committed: "Poeng innsendt" - points_resolved: "Poeng løst" - points_to_accept: "Poeng ikke akseptert" - points_to_resolve: "Poeng ikke løst" project_module_backlogs: "Forsinkelser" - rb_label_copy_tasks: "Kopier arbeidspakker" - rb_label_copy_tasks_all: "Alle" - rb_label_copy_tasks_none: "Ingen" - rb_label_copy_tasks_open: "Åpne" - rb_label_link_to_original: "Inkluder lenke til den originale historien" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "gjenstående arbeid" - required_burn_rate_hours: "Nødvendig brennetid (timer)" - required_burn_rate_points: "Nødvendig brennetid (poeng)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Kolonne i forsinkelse" version_settings_display_option_left: "venstre" version_settings_display_option_none: "ingen" diff --git a/modules/backlogs/config/locales/crowdin/pl.yml b/modules/backlogs/config/locales/crowdin/pl.yml index ed8f08b125b..0e3b923e5d2 100644 --- a/modules/backlogs/config/locales/crowdin/pl.yml +++ b/modules/backlogs/config/locales/crowdin/pl.yml @@ -25,6 +25,8 @@ pl: description: "Moduł ten dodaje funkcje umożliwiające zwinnym zespołom pracę z OpenProject w projektach Scrum." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Pozycja" story_points: "Historia Punktów" @@ -43,128 +45,101 @@ pl: attributes: task_type: "Typ zadania" backlogs: - add_new_story: "Nowe Story" any: "którekolwiek" - backlog_settings: "Ustawienia Backlogs" - burndown_graph: "Wykres Burndown" - card_paper_size: "Rozmiar papieru do drukowania kart" - chart_options: "Opcje wykresu" - close: "Zamknij" - column_width: "Szerokość kolumny:" - date: "Dzień" + column_width: "Column width" definition_of_done: "Definicja Zrobione" - generating_chart: "Generowanie wykresu..." - hours: "Godziny" impediment: "Przeszkoda" label_versions_default_fold_state: "Pokaż zwinięte wersje" caption_versions_default_fold_state: "Wersje nie będą domyślnie rozwijane podczas przeglądania backlogów. Każdą z nich należy rozwinąć ręcznie." work_package_is_closed: "Zestaw Zadań będzie gotowy, kiedy" label_is_done_status: "Status %{status_name} oznacza zrobiony" - no_burndown_data: "Brak danych dostępnych danych spalania. Trzeba ustawić daty początku i końca sprintu." - points: "Punkty" + points_label: + one: "point" + few: "points" + many: "points" + other: "points" positions_could_not_be_rebuilt: "Pozycje nie mogą zostać przebudowane." positions_rebuilt_successfully: "Pozycje zostały przebudowane pomyślnie." - properties: "Właściwości" rebuild: "Przebuduj" rebuild_positions: "Przebuduj pozycje" remaining_hours: "Pozostała praca" - remaining_hours_ideal: "Pozostałe prace (idealnie)" show_burndown_chart: "Burndown Chart" story: "Story" - story_points: "Historia Punktów" - story_points_ideal: "Story Pointy (idealne)" + story_points: + one: "%{count} story point" + few: "%{count} story points" + many: "%{count} story points" + other: "%{count} story points" task: "Zadanie" task_color: "Kolor zadania" unassigned: "Nieprzypisane" user_preference: header_backlogs: "Moduł backlogów" button_update_backlogs: "Zaktualizuj moduł backlogów" - x_more: "%{count} więcej..." - backlogs_active: "aktywny" - backlogs_any: "którekolwiek" - backlogs_inactive: "Projekt pokazuje brak aktywności" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Punkty burn up/down" backlogs_product_backlog: "Backlog produktu" - backlogs_product_backlog_is_empty: "Backlog produktu jest pusty" - backlogs_product_backlog_unsized: "W górze backlogu produktu pozostają niesortowane historie" - backlogs_sizing_inconsistent: "Rozmiary Story różnią się ze swoimi estymatami" - backlogs_sprint_notes_missing: "Zamknięte sprinty bez notatek retrospektywy/przeglądu" - backlogs_sprint_unestimated: "Zamknięte bądź aktywne sprinty z nieestymowanymi historiami" - backlogs_sprint_unsized: "Projekt ma historie w aktywnym lub niedawno zamkniętym sprintach, które nie były oszacowane" - backlogs_sprints: "Sprinty" backlogs_story: "Story" backlogs_story_type: "Typy Story" backlogs_task: "Zadanie" backlogs_task_type: "Typ zadania" - backlogs_velocity_missing: "Nie można obliczyć prędkości dla tego projektu" - backlogs_velocity_varies: "Prędkość w sprintach istotnie się różni" backlogs_wiki_template: "Szablon dla strony wiki sprintu" - backlogs_empty_title: "Nie ma wersji zdefiniowanej do użycia w zaległościach" - backlogs_empty_action_text: "Aby rozpocząć pracę z zaległościami, utworzyć nową wersję i przypisać ją do kolumna w zaległości." - button_edit_wiki: "Edytuj stronę wiki" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "nie może być również typem historii" - error_intro_plural: "Wystąpiły następujące błędy:" - error_intro_singular: "Wystąpił następujący błąd:" - error_outro: "Popraw błędy powyżej przed ponownym złożeniem." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "idealne" - inclusion: "nie znajduje się na liście" - label_back_to_project: "Przejdź do strony projektu" - label_backlog: "Backlog" label_backlogs: "Backlogi" label_backlogs_unconfigured: "Jeszcze nie skonfigurowałeś backlogów. Przejdź do %{administration} > %{plugins}, następnie kliknij link %{configure} dla otrzymania tego dodatku. Po ustawieniu pól, wróć na tę stronę, aby zacząć korzystanie z narzędzia." label_blocks_ids: "Identyfikatory zablokowanych pakietów prac" - label_burndown: "Burndown" label_column_in_backlog: "Kolumna w backlogu" - label_hours: "godziny" - label_work_package_hierarchy: "Hierarchia pakietów prac" - label_master_backlog: "Master Backlog" - label_not_prioritized: "nie priorytetowo" - label_points: "punkty" label_points_burn_down: "W dół" label_points_burn_up: "W górę" - label_product_backlog: "backlog produktu" - label_select_all: "Wybierz wszystko" label_select_type: "Wybierz typ" label_select_types: "Wybierz typy" label_selected_type: "Wybrany typ" label_selected_types: "Wybrane typy" - label_sprint_backlog: "backlog sprintu" - label_sprint_cards: "Eksportuj karty" label_sprint_impediments: "Przeszkody sprintu" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Prędkość %{velocity} w oparciu o %{sprints} sprinty ze średnią dni %{days}" - label_stories: "Historie" - label_stories_tasks: "Historie/Zanania" label_task_board: "Panel zadań" - label_version_setting: "Wersje" - label_version: 'Wersja' - label_webcal: "Kanał Webcal" - label_wiki: "Wiki" permission_view_master_backlog: "Wyświetl master backlog" permission_view_taskboards: "Wyświetl panel zadań" permission_select_done_statuses: "Wybierz wykonane statusy" permission_update_sprints: "Aktualizuj sprint" - points_accepted: "punkty zaakceptowane" - points_committed: "punkty zatwierdzone" - points_resolved: "punkty wyjaśnione" - points_to_accept: "punkty nie zaakceptowane" - points_to_resolve: "punkty nie wyjaśnione" project_module_backlogs: "Backlogi" - rb_label_copy_tasks: "Kopiuj Zestaw Zadań" - rb_label_copy_tasks_all: "Wszystko" - rb_label_copy_tasks_none: "Żaden" - rb_label_copy_tasks_open: "Otwórz" - rb_label_link_to_original: "Podepnij łącze do oryginalnego wątku" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "pozostała praca" - required_burn_rate_hours: "wskaźnik wymaganego czasu nagrywania (godziny)" - required_burn_rate_points: "wskaźnik wymaganego czasu nagrywania (punkty)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Kolumna w backlogu" version_settings_display_option_left: "w lewo" version_settings_display_option_none: "żaden" diff --git a/modules/backlogs/config/locales/crowdin/pt-BR.yml b/modules/backlogs/config/locales/crowdin/pt-BR.yml index dcc6afbbb81..5366dd9fa88 100644 --- a/modules/backlogs/config/locales/crowdin/pt-BR.yml +++ b/modules/backlogs/config/locales/crowdin/pt-BR.yml @@ -25,6 +25,8 @@ pt-BR: description: "Este módulo acrescenta recursos que permitem que as equipes ágeis trabalhem com o OpenProject em projetos Scrum." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Posição" story_points: "Pontos de história" @@ -43,128 +45,97 @@ pt-BR: attributes: task_type: "Tipo de tarefa" backlogs: - add_new_story: "Nova história" any: "qualquer" - backlog_settings: "Configurações de backlogs" - burndown_graph: "Gráfico de Burndown" - card_paper_size: "Tamanho do papel para impressão de cartões" - chart_options: "Opções de gráfico" - close: "Fechar" - column_width: "Largura da Coluna:" - date: "Dia" + column_width: "Column width" definition_of_done: "Definição de pronto" - generating_chart: "Gerando gráfico..." - hours: "Horas" impediment: "Impedimento" label_versions_default_fold_state: "Mostrar versões em modo fechado" caption_versions_default_fold_state: "As versões não serão expandidas por padrão ao visualizar backlogs. Cada uma deve ser expandida manualmente." work_package_is_closed: "Pacote de trabalho está pronto, quando" label_is_done_status: "Situação %{status_name} significa pronto" - no_burndown_data: "Nenhum dado de burndown disponível. É necessário definir as datas de início e fim da sprint." - points: "Pontos" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "Posições não poderiam ser reconstruídas." positions_rebuilt_successfully: "Posições reconstruídas com sucesso." - properties: "Propriedades" rebuild: "Reconstruir" rebuild_positions: "Reconstruir posições" remaining_hours: "Trabalho restante" - remaining_hours_ideal: "Trabalho restante (ideal)" show_burndown_chart: "Gráfico de Burndown" story: "História" - story_points: "Pontos de História" - story_points_ideal: "Pontos de história (ideal)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "Tarefa" task_color: "Cor da tarefa" unassigned: "Não atribuída" user_preference: header_backlogs: "Módulo de backlogs" button_update_backlogs: "Atualizar módulo de backlogs" - x_more: "%{count} mais..." - backlogs_active: "ativo" - backlogs_any: "qualquer" - backlogs_inactive: "Projeto não mostra nenhuma atividade" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Pontos burn up/down" backlogs_product_backlog: "Backlog do produto" - backlogs_product_backlog_is_empty: "Backlog do produto está vazio" - backlogs_product_backlog_unsized: "O topo do backlog de produto tem histórias não dimensionadas" - backlogs_sizing_inconsistent: "Tamanhos das histórias contrastam com suas estimativas" - backlogs_sprint_notes_missing: "Sprints encerradas sem notas de retrospectiva/revisão" - backlogs_sprint_unestimated: "Sprints encerradas ou ativas com histórias não estimadas" - backlogs_sprint_unsized: "O projeto tem histórias em sprints ativas ou recentemente encerradas que não foram dimensionadas" - backlogs_sprints: "Sprints" backlogs_story: "História" backlogs_story_type: "Tipos de história" backlogs_task: "Tarefa" backlogs_task_type: "Tipo de tarefa" - backlogs_velocity_missing: "Nenhuma velocidade foi calculada para este projeto" - backlogs_velocity_varies: "A velocidade varia significativamente entre as sprints" backlogs_wiki_template: "Modelo para página wiki da sprint" - backlogs_empty_title: "Não há versões definidas para serem usadas nos backlogs" - backlogs_empty_action_text: "Para iniciar com backlogs, crie uma nova versão e atribua a uma coluna de backlogs." - button_edit_wiki: "Editar página wiki" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "também não pode ser do tipo história" - error_intro_plural: "Foram encontrados os seguintes erros :" - error_intro_singular: "Foi encontrado o seguinte erro:" - error_outro: "Por favor, corrija os erros acima antes de enviar novamente." - event_sprint_description: "%{summary}: %{url}%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ideal" - inclusion: "não está incluído na lista" - label_back_to_project: "Voltar à página do projeto" - label_backlog: "Backlog" label_backlogs: "Backlogs" label_backlogs_unconfigured: "Você ainda não configurou o Backlog. Por favor, vá para %{administration} > %{plugins} e, em seguida, clique em %{configure} o link para este plugin. Uma vez que você definiu os campos, volte a esta página para começar a usar a ferramenta." label_blocks_ids: "IDs dos pacotes de trabalho bloqueados" - label_burndown: "Burndown" label_column_in_backlog: "Coluna no backlog" - label_hours: "horas" - label_work_package_hierarchy: "Hierarquia de Pacote de Trabalho" - label_master_backlog: "Backlog principal" - label_not_prioritized: "não priorizado" - label_points: "pontos" label_points_burn_down: "Abaixo" label_points_burn_up: "Acima" - label_product_backlog: "Backlog do produto" - label_select_all: "Selecionar tudo" label_select_type: "Selecione um tipo" label_select_types: "Selecionar tipos" label_selected_type: "Tipo selecionado" label_selected_types: "Tipos selecionados" - label_sprint_backlog: "backlog da sprint" - label_sprint_cards: "Exportar cartões" label_sprint_impediments: "Impedimentos da Sprint" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Velocidade %{velocity}, baseado em %{sprints} sprints, com uma média de %{days} dias" - label_stories: "Histórias" - label_stories_tasks: "Histórias/tarefas" label_task_board: "Quadro de tarefas" - label_version_setting: "Versões" - label_version: 'Versão' - label_webcal: "Webcal Feed" - label_wiki: "Wiki" permission_view_master_backlog: "Visualizar backlog principal" permission_view_taskboards: "Visualizar quadro de tarefas" permission_select_done_statuses: "Selecione situações concluídas" permission_update_sprints: "Editar sprints" - points_accepted: "pontos aceitos" - points_committed: "pontos comprometidos" - points_resolved: "pontos resolvidos" - points_to_accept: "pontos não aceitos" - points_to_resolve: "pontos não resolvidos" project_module_backlogs: "Backlogs" - rb_label_copy_tasks: "Copiar pacotes de trabalho" - rb_label_copy_tasks_all: "Todos" - rb_label_copy_tasks_none: "Nenhum" - rb_label_copy_tasks_open: "Aberto" - rb_label_link_to_original: "Incluir link para a história original" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "trabalho restante" - required_burn_rate_hours: "burn rate necessário (horas)" - required_burn_rate_points: "burn rate necessário (pontos)" - todo_work_package_description: "%{summary}: %{url}%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Coluna no backlog" version_settings_display_option_left: "esquerda" version_settings_display_option_none: "nenhum" diff --git a/modules/backlogs/config/locales/crowdin/pt-PT.yml b/modules/backlogs/config/locales/crowdin/pt-PT.yml index 8e504a04df0..e6925ed37b3 100644 --- a/modules/backlogs/config/locales/crowdin/pt-PT.yml +++ b/modules/backlogs/config/locales/crowdin/pt-PT.yml @@ -25,6 +25,8 @@ pt-PT: description: "Este módulo acrescenta funcionalidades que permitem às equipas Agile trabalhar com o OpenProject em projetos Scrum." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Posição" story_points: "Pontos de histórico" @@ -43,128 +45,97 @@ pt-PT: attributes: task_type: "Tipo de tarefa" backlogs: - add_new_story: "Nova história" any: "qualquer" - backlog_settings: "Definições de backlogs" - burndown_graph: "Gráfico de Burndown" - card_paper_size: "Tamanho do papel para impressão de cartões" - chart_options: "Opções de gráficos" - close: "Fechar" - column_width: "Largura da coluna:" - date: "Dia" + column_width: "Column width" definition_of_done: "Definição de feito" - generating_chart: "A gerar gráfico..." - hours: "Horas" impediment: "Impedimento" label_versions_default_fold_state: "Mostrar versões dobradas" caption_versions_default_fold_state: "Versões não serão expandidas por predefinição quando visualizar os backlogs. Cada uma tem de ser expandida manualmente." work_package_is_closed: "Pacote de trabalho está feito quando" label_is_done_status: "O estado %{status_name} significa terminado" - no_burndown_data: "Não há dados de burndown disponíveis. É necessário ter o conjunto de datas de início e final de sprint." - points: "Pontos" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "As posições não puderam ser reconstruídas." positions_rebuilt_successfully: "Posições reconstruídas com êxito." - properties: "Propriedades" rebuild: "Reconstruir" rebuild_positions: "Reconstruir as posições" remaining_hours: "Trabalho restante" - remaining_hours_ideal: "Trabalho restante (ideal)" show_burndown_chart: "Gráfico de Burndown" story: "História" - story_points: "Pontos de histórico" - story_points_ideal: "Pontos de história (ideal)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "Tarefa" task_color: "Cor de tarefa" unassigned: "Não atribuído" user_preference: header_backlogs: "Módulo de backlogs" button_update_backlogs: "Atualizar o módulo de backlogs" - x_more: "%{count} mais..." - backlogs_active: "ativo" - backlogs_any: "qualquer" - backlogs_inactive: "Projeto não mostra nenhuma atividade" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Pontos queimam para cima/para baixo" backlogs_product_backlog: "Backlog do produto" - backlogs_product_backlog_is_empty: "O backlog do produto está vazio" - backlogs_product_backlog_unsized: "O topo do backlog do produto tem histórias sem tamanho" - backlogs_sizing_inconsistent: "Os tamanhos de histórias variam em relação às suas estimativas" - backlogs_sprint_notes_missing: "Sprints fechados sem revisão retrospectiva/notas" - backlogs_sprint_unestimated: "Sprints ativos ou fechados com histórias não estimadas" - backlogs_sprint_unsized: "Projeto tem histórias em sprints ativos ou recentemente fechados que não estavam dimensionados" - backlogs_sprints: "Sprints" backlogs_story: "História" backlogs_story_type: "Tipo de histórias" backlogs_task: "Tarefa" backlogs_task_type: "Tipo de tarefa" - backlogs_velocity_missing: "Não foi possível calcular velocidade para este projeto" - backlogs_velocity_varies: "A velocidade varia significativamente ao longo dos sprints" backlogs_wiki_template: "Modelo para a página wiki de sprint" - backlogs_empty_title: "Não há versões definidas para serem utilizadas em backlogs" - backlogs_empty_action_text: "Para começar a utilizar os backlogs, crie uma nova versão e atribua-a a uma coluna de backlogs." - button_edit_wiki: "Editar página wiki" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "não pode ser também um tipo de história" - error_intro_plural: "Os seguintes erros foram encontrados:" - error_intro_singular: "Foi encontrado o seguinte erro:" - error_outro: "Por favor, corrija os erros acima antes de enviar novamente." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ideal" - inclusion: "não está incluído na lista" - label_back_to_project: "Voltar à página do projeto" - label_backlog: "Backlog" label_backlogs: "Backlogs" label_backlogs_unconfigured: "Ainda não configurou os backlogs. Vá a %{administration} > %{plugins}, e depois clique no link %{configure} para este plugin. Após definir os campos, volte a esta página para começar a utilizar a ferramenta." label_blocks_ids: "Identificações de pacotes de trabalho bloqueados" - label_burndown: "Burndown" label_column_in_backlog: "Coluna no backlog" - label_hours: "horas" - label_work_package_hierarchy: "Hierarquia de pacotes de trabalho" - label_master_backlog: "Backlog principal" - label_not_prioritized: "não priorizada" - label_points: "pontos" label_points_burn_down: "Abaixo" label_points_burn_up: "Acima" - label_product_backlog: "backlog do produto" - label_select_all: "Seleccionar todos" label_select_type: "Selecionar um tipo" label_select_types: "Selecionar tipos" label_selected_type: "Tipo selecionado" label_selected_types: "Tipos selecionados" - label_sprint_backlog: "backlog de sprint" - label_sprint_cards: "Exportar cartões" label_sprint_impediments: "Impedimentos de Sprint" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Velocidade %{velocity}, baseada em %{sprints} sprints com uma média de %{days} dias" - label_stories: "Histórias" - label_stories_tasks: "Histórias/tarefas" label_task_board: "Quadro de tarefas" - label_version_setting: "Versões" - label_version: 'Versão' - label_webcal: "Webcal Feed" - label_wiki: "Wiki" permission_view_master_backlog: "Ver o backlog principal" permission_view_taskboards: "Ver quadros de tarefas" permission_select_done_statuses: "Selecione os estados concluídos" permission_update_sprints: "Atualizar sprints" - points_accepted: "pontos aceites" - points_committed: "pontos comprometidos" - points_resolved: "pontos resolvidos" - points_to_accept: "pontos não aceites" - points_to_resolve: "pontos não resolvidos" project_module_backlogs: "Backlogs" - rb_label_copy_tasks: "Copiar pacotes de trabalho" - rb_label_copy_tasks_all: "Todos" - rb_label_copy_tasks_none: "Nenhum" - rb_label_copy_tasks_open: "Abrir" - rb_label_link_to_original: "Incluir o link para a história original" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "trabalho restante" - required_burn_rate_hours: "taxa de queimadura necessária (horas)" - required_burn_rate_points: "taxa de queimadura necessários (pontos)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Coluna no backlog" version_settings_display_option_left: "esquerda" version_settings_display_option_none: "nenhum" diff --git a/modules/backlogs/config/locales/crowdin/ro.yml b/modules/backlogs/config/locales/crowdin/ro.yml index 4b6260593aa..55438a8b9f1 100644 --- a/modules/backlogs/config/locales/crowdin/ro.yml +++ b/modules/backlogs/config/locales/crowdin/ro.yml @@ -25,6 +25,8 @@ ro: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Poziție" story_points: "Puncte" @@ -43,128 +45,99 @@ ro: attributes: task_type: "Tip de sarcină" backlogs: - add_new_story: "Poveste nouă" any: "Oricare" - backlog_settings: "Setări" - burndown_graph: "Graficul Burndown" - card_paper_size: "Dimensiunea hârtiei pentru imprimarea cardurilor" - chart_options: "Opțiuni" - close: "Închide" - column_width: "Lăţime:" - date: "Zi" + column_width: "Column width" definition_of_done: "Procent realizat" - generating_chart: "Raport în curs de generare..." - hours: "Ore" impediment: "Impediment" label_versions_default_fold_state: "Afișare versiuni complete" caption_versions_default_fold_state: "Versiunile nu vor fi extinse în mod implicit la vizualizarea restanțelor. Fiecare versiune trebuie să fie extinsă manual." work_package_is_closed: "Pachetul de lucru este finalizat, atunci când" label_is_done_status: "Starea %{status_name} înseamnă terminat" - no_burndown_data: "Nu sunt disponibile date privind arderea în vegetație. Este necesar să fie stabilite datele de început și de sfârșit ale sprintului." - points: "Puncte" + points_label: + one: "point" + few: "points" + other: "points" positions_could_not_be_rebuilt: "Pozițiile nu au putut fi reconstruite." positions_rebuilt_successfully: "Toate elementele au fost șterse cu succes" - properties: "Proprietăți" rebuild: "Reconstruiți" rebuild_positions: "Reconstruiți" remaining_hours: "Muncă rămasă" - remaining_hours_ideal: "Munca rămasă (ideal)" show_burndown_chart: "Size Chart" story: "Articol" - story_points: "Puncte cerință" - story_points_ideal: "Puncte" + story_points: + one: "%{count} story point" + few: "%{count} story points" + other: "%{count} story points" task: "Sarcină" task_color: "Sarcină" unassigned: "Neasociate" user_preference: header_backlogs: "Modul Backlog-uri" button_update_backlogs: "Actualizează modul backlogs" - x_more: "%{count} mai mult..." - backlogs_active: "activ" - backlogs_any: "oricare" - backlogs_inactive: "Proiectul nu prezintă nicio activitate" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Arderea punctelor în sus/jos" backlogs_product_backlog: "Fișa produsului" - backlogs_product_backlog_is_empty: "Versiune produs" - backlogs_product_backlog_unsized: "Partea de sus a backlogului de produs are povești nedimensionate" - backlogs_sizing_inconsistent: "Dimensiunile poveștilor variază față de estimările lor" - backlogs_sprint_notes_missing: "Sprinturi închise fără note retrospective/de revizuire" - backlogs_sprint_unestimated: "Sprinturi închise sau active cu povești neestimate" - backlogs_sprint_unsized: "Proiectul are povești pe sprinturi active sau recent închise care nu au fost dimensionate" - backlogs_sprints: "Sprinturi" backlogs_story: "Cerință" backlogs_story_type: "Tipuri de povești" backlogs_task: "Sarcină" backlogs_task_type: "Sarcină" - backlogs_velocity_missing: "Nu a putut fi calculată nicio viteză pentru acest proiect" - backlogs_velocity_varies: "Viteza variază semnificativ pe parcursul sprinturilor" backlogs_wiki_template: "Șablon pentru pagina wiki a sprintului" - backlogs_empty_title: "Nu sunt definite versiuni care să fie utilizate în dosarele de așteptare" - backlogs_empty_action_text: "Pentru a începe să folosiți agendele, creați o nouă versiune și atribuiți-o unei coloane de agende." - button_edit_wiki: "Editați pagina wiki" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "nu poate fi și un tip poveste" - error_intro_plural: "Au fost întâlnite următoarele erori:" - error_intro_singular: "Eroare" - error_outro: "Te rog să corectezi erorile de mai sus înainte de a le trimite din nou." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ideal" - inclusion: "nu este inclusă în listă" - label_back_to_project: "Înapoi la pagina precedentă" - label_backlog: "Lista de așteptare" label_backlogs: "Restanțe" label_backlogs_unconfigured: "Nu ai configurat încă Backlogs. Te rog să mergi la %{administration} > %{plugins}, apoi dă clic pe link-ul %{configure} pentru acest plugin. După ce ai configurat câmpurile, revino la această pagină pentru a începe să utilizezi instrumentul." label_blocks_ids: "ID-urile pachetelor de lucru blocate" - label_burndown: "Burndown" label_column_in_backlog: "Coloană în backlog" - label_hours: "ore" - label_work_package_hierarchy: "Ierarhia pachetelor de lucru" - label_master_backlog: "Master Backlog" - label_not_prioritized: "nu este prioritară" - label_points: "puncte" label_points_burn_down: "Jos" label_points_burn_up: "Sus" - label_product_backlog: "backlog de produse" - label_select_all: "Setectează Tot" label_select_type: "Selectează un tip" label_select_types: "Selectează tipuri" label_selected_type: "Tip selectat" label_selected_types: "Tipuri selectate" - label_sprint_backlog: "sprint backlog" - label_sprint_cards: "Carduri de export" label_sprint_impediments: "Impedimentele Sprint" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Viteza %{velocity}, bazată pe %{sprints} sprinturi cu o medie de %{days} zile" - label_stories: "Povestiri" - label_stories_tasks: "Povești/ Sarcini" label_task_board: "Tablă de sarcini" - label_version_setting: "Versiuni" - label_version: 'Versiune' - label_webcal: "Webcal Feed" - label_wiki: "Wiki" permission_view_master_backlog: "Vizualizare master backlog" permission_view_taskboards: "Vizualizați tablourile de sarcini" permission_select_done_statuses: "Selectează stările realizate" permission_update_sprints: "Sprinturi" - points_accepted: "puncte acceptate" - points_committed: "puncte comise" - points_resolved: "puncte rezolvate" - points_to_accept: "puncte neacceptate" - points_to_resolve: "puncte nesoluționate" project_module_backlogs: "Restanțe" - rb_label_copy_tasks: "Copierea pachetelor de lucru" - rb_label_copy_tasks_all: "Toate" - rb_label_copy_tasks_none: "Niciuna" - rb_label_copy_tasks_open: "Deschis" - rb_label_link_to_original: "Includeți un link către povestea originală" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "muncă rămasă" - required_burn_rate_hours: "rata de ardere necesară (ore)" - required_burn_rate_points: "rata de ardere necesară (puncte)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Coloană în restanță" version_settings_display_option_left: "stanga" version_settings_display_option_none: "niciuna" diff --git a/modules/backlogs/config/locales/crowdin/ru.yml b/modules/backlogs/config/locales/crowdin/ru.yml index 224c3f8f45a..0c56d8c31e1 100644 --- a/modules/backlogs/config/locales/crowdin/ru.yml +++ b/modules/backlogs/config/locales/crowdin/ru.yml @@ -25,6 +25,8 @@ ru: description: "Этот модуль добавляет функции, позволяющие agile-командам работать с OpenProject в Scrum проектах." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Позиция" story_points: "Стори поинты" @@ -43,128 +45,101 @@ ru: attributes: task_type: "Тип задачи" backlogs: - add_new_story: "Новая история" any: "любой" - backlog_settings: "Настройки Backlogs" - burndown_graph: "Диаграмма выгорания задач" - card_paper_size: "Размер бумаги для печати карточек" - chart_options: "Параметры диаграммы" - close: "Закрыть" - column_width: "Ширина колонки:" - date: "День" + column_width: "Column width" definition_of_done: "Определение термина \"Завершено\"" - generating_chart: "Создание отчета..." - hours: "Часы" impediment: "Препятствие" label_versions_default_fold_state: "Показать свернутые версии" caption_versions_default_fold_state: "Версии не будут разворачиваться по умолчанию при просмотре бэклогов. Каждая версия должна быть развернута вручную." work_package_is_closed: "Пакет работ завершен, когда" label_is_done_status: "Статус %{status_name} означает завершение," - no_burndown_data: "Нет данных по выгоранию. Необходимо установить даты начала и конца спринта." - points: "Точки" + points_label: + one: "point" + few: "points" + many: "points" + other: "points" positions_could_not_be_rebuilt: "Не удалось восстановить позиции." positions_rebuilt_successfully: "Позиции успешно восстановлены." - properties: "Свойства" rebuild: "Восстановить" rebuild_positions: "Восстановить позиции" remaining_hours: "Оставшиеся часы" - remaining_hours_ideal: "Оставшиеся часы (идеал)" show_burndown_chart: "Диаграмма выгорания задач" story: "История" - story_points: "Исторические точки" - story_points_ideal: "Исторические точки (идеал)" + story_points: + one: "%{count} story point" + few: "%{count} story points" + many: "%{count} story points" + other: "%{count} story points" task: "Задача" task_color: "Цвет задачи" unassigned: "Нераспределенные" user_preference: header_backlogs: "Модуль бэклогов" button_update_backlogs: "Обновить модуль бэклогов" - x_more: "Еще %{count}..." - backlogs_active: "активно" - backlogs_any: "любой" - backlogs_inactive: "Мероприятия по проекту не показаны" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Точки выгорания вверх/вниз" backlogs_product_backlog: "Требования к продукту, с приоритетами" - backlogs_product_backlog_is_empty: "Требования к продукту не указаны" - backlogs_product_backlog_unsized: "Верхняя позиция требований к продукту имеет неотсортированные истории" - backlogs_sizing_inconsistent: "Размеры историй разнятся с их прогнозами" - backlogs_sprint_notes_missing: "Закрытые спринты без ретроспективных/обзорных заметок" - backlogs_sprint_unestimated: "Закрытые или активные спринты с непрогнозируемыми историями" - backlogs_sprint_unsized: "В проекте имеются истории в активных или недавно закрытых спринтах, размер которых не был определен" - backlogs_sprints: "Спринты" backlogs_story: "История" backlogs_story_type: "Типы историй" backlogs_task: "Задача" backlogs_task_type: "Тип задачи" - backlogs_velocity_missing: "Нельзя рассчитать скорость для этого проекта" - backlogs_velocity_varies: "Скорость спринтов значительно различается" backlogs_wiki_template: "Шаблон для wiki-страницы по спринту" - backlogs_empty_title: "Для бэклога работ не задано ни одного этапа" - backlogs_empty_action_text: "Чтобы пользоваться модулем Бэклоги, создайте этап и назначьте его столбцу бэклога." - button_edit_wiki: "Редактировать wiki-страницу" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "не может также быть типом истории" - error_intro_plural: "Найдены следующие ошибки:" - error_intro_singular: "Найдена следующая ошибка:" - error_outro: "Исправьте вышеуказанные ошибки перед повторной отправкой." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "идеал" - inclusion: "не входит в список" - label_back_to_project: "Назад к странице проекта" - label_backlog: "Бэклог" label_backlogs: "Бэклоги" label_backlogs_unconfigured: "Вы еще не настроили Невыполненные работы. Перейдите на страницу %{administration} > %{plugins}, а затем нажмите на ссылку %{configure} для получения этого дополнения. После того как вы настроите поля, возвратитесь на эту страницу, чтобы начать пользоваться инструментом." label_blocks_ids: "Идентификаторы заблокированных рабочих пакетов" - label_burndown: "Выгорание" label_column_in_backlog: "Колонка в бэклоге" - label_hours: "часы" - label_work_package_hierarchy: "Иерархия пакета работ" - label_master_backlog: "Основной бэклог" - label_not_prioritized: "нет приоритетов" - label_points: "точки" label_points_burn_down: "Вниз" label_points_burn_up: "Вверх" - label_product_backlog: "бэклог продукта" - label_select_all: "Выбрать все" label_select_type: "Выберите тип" label_select_types: "Выберите типы" label_selected_type: "Выбранный тип" label_selected_types: "Выбранные типы" - label_sprint_backlog: "список невыполненных работ спринта" - label_sprint_cards: "Экспортировать карточки" label_sprint_impediments: "Препятствия спринта" - label_sprint_name: "Спринт \"%{name}\"" - label_sprint_velocity: "Скорость %{velocity} из расчета %{sprints} спринтов со средней продолжительностью %{days} дней" - label_stories: "Истории" - label_stories_tasks: "Истории/задачи" label_task_board: "Панель задач" - label_version_setting: "Версии" - label_version: 'Версия' - label_webcal: "Лента Webcal" - label_wiki: "Wiki" permission_view_master_backlog: "Просмотреть главную невыполненную работу" permission_view_taskboards: "Просмотреть панели задач" permission_select_done_statuses: "Выберите завершенные статусы" permission_update_sprints: "Обновить спринты" - points_accepted: "принятые пункты" - points_committed: "порученные точки" - points_resolved: "решенные пункты" - points_to_accept: "непринятые пункты" - points_to_resolve: "нерешенные пункты" project_module_backlogs: "Бэклоги" - rb_label_copy_tasks: "Копировать пакеты работ" - rb_label_copy_tasks_all: "Bсе" - rb_label_copy_tasks_none: "Нет" - rb_label_copy_tasks_open: "Открыть" - rb_label_link_to_original: "Включить ссылку на первоначальную историю" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "оставшиеся часы" - required_burn_rate_hours: "требуемая скорость сгорания (часы)" - required_burn_rate_points: "требуемая скорость сгорания (точки)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Колонка в бэклоге" version_settings_display_option_left: "влево" version_settings_display_option_none: "нет" diff --git a/modules/backlogs/config/locales/crowdin/rw.yml b/modules/backlogs/config/locales/crowdin/rw.yml index e577927a48c..e3a3ad332a2 100644 --- a/modules/backlogs/config/locales/crowdin/rw.yml +++ b/modules/backlogs/config/locales/crowdin/rw.yml @@ -25,6 +25,8 @@ rw: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Position" story_points: "Story Points" @@ -43,128 +45,97 @@ rw: attributes: task_type: "Task type" backlogs: - add_new_story: "New Story" any: "any" - backlog_settings: "Backlogs settings" - burndown_graph: "Burndown Graph" - card_paper_size: "Paper size for card printing" - chart_options: "Chart options" - close: "Close" - column_width: "Column width:" - date: "Day" + column_width: "Column width" definition_of_done: "Definition of Done" - generating_chart: "Generating Graph..." - hours: "Hours" impediment: "Impediment" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." work_package_is_closed: "Work package is done, when" label_is_done_status: "Status %{status_name} means done" - no_burndown_data: "No burndown data available. It is necessary to have the sprint start- and end dates set." - points: "Points" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "Positions could not be rebuilt." positions_rebuilt_successfully: "Positions rebuilt successfully." - properties: "Properties" rebuild: "Rebuild" rebuild_positions: "Rebuild positions" remaining_hours: "Remaining work" - remaining_hours_ideal: "Remaining work (ideal)" show_burndown_chart: "Burndown Chart" story: "Story" - story_points: "Story Points" - story_points_ideal: "Story Points (ideal)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "Task" task_color: "Task color" unassigned: "Unassigned" user_preference: header_backlogs: "Backlogs module" button_update_backlogs: "Update backlogs module" - x_more: "%{count} more..." - backlogs_active: "active" - backlogs_any: "any" - backlogs_inactive: "Project shows no activity" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Points burn up/down" backlogs_product_backlog: "Product backlog" - backlogs_product_backlog_is_empty: "Product backlog is empty" - backlogs_product_backlog_unsized: "The top of the product backlog has unsized stories" - backlogs_sizing_inconsistent: "Story sizes vary against their estimates" - backlogs_sprint_notes_missing: "Closed sprints without retrospective/review notes" - backlogs_sprint_unestimated: "Closed or active sprints with unestimated stories" - backlogs_sprint_unsized: "Project has stories on active or recently closed sprints that were not sized" - backlogs_sprints: "Sprints" backlogs_story: "Story" backlogs_story_type: "Story types" backlogs_task: "Task" backlogs_task_type: "Task type" - backlogs_velocity_missing: "No velocity could be calculated for this project" - backlogs_velocity_varies: "Velocity varies significantly over sprints" backlogs_wiki_template: "Template for sprint wiki page" - backlogs_empty_title: "No versions are defined to be used in backlogs" - backlogs_empty_action_text: "To get started with backlogs, create a new version and assign it to a backlogs column." - button_edit_wiki: "Edit wiki page" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "The following errors were encountered:" - error_intro_singular: "The following error was encountered:" - error_outro: "Please correct the above errors before submitting again." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ideal" - inclusion: "is not included in the list" - label_back_to_project: "Back to project page" - label_backlog: "Backlog" label_backlogs: "Backlogs" label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "IDs of blocked work packages" - label_burndown: "Burndown" label_column_in_backlog: "Column in backlog" - label_hours: "hours" - label_work_package_hierarchy: "Work package Hierarchy" - label_master_backlog: "Master Backlog" - label_not_prioritized: "not prioritized" - label_points: "points" label_points_burn_down: "Down" label_points_burn_up: "Up" - label_product_backlog: "product backlog" - label_select_all: "Select all" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "sprint backlog" - label_sprint_cards: "Export cards" label_sprint_impediments: "Sprint Impediments" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Velocity %{velocity}, based on %{sprints} sprints with an average %{days} days" - label_stories: "Stories" - label_stories_tasks: "Stories/Tasks" label_task_board: "Task board" - label_version_setting: "Versions" - label_version: 'Version' - label_webcal: "Webcal Feed" - label_wiki: "Wiki" permission_view_master_backlog: "View master backlog" permission_view_taskboards: "View taskboards" permission_select_done_statuses: "Select done statuses" permission_update_sprints: "Update sprints" - points_accepted: "points accepted" - points_committed: "points committed" - points_resolved: "points resolved" - points_to_accept: "points not accepted" - points_to_resolve: "points not resolved" project_module_backlogs: "Backlogs" - rb_label_copy_tasks: "Copy work packages" - rb_label_copy_tasks_all: "All" - rb_label_copy_tasks_none: "None" - rb_label_copy_tasks_open: "Open" - rb_label_link_to_original: "Include link to original story" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "remaining work" - required_burn_rate_hours: "required burn rate (hours)" - required_burn_rate_points: "required burn rate (points)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Column in backlog" version_settings_display_option_left: "left" version_settings_display_option_none: "none" diff --git a/modules/backlogs/config/locales/crowdin/si.yml b/modules/backlogs/config/locales/crowdin/si.yml index 3ff7a3fc621..c517654e8a5 100644 --- a/modules/backlogs/config/locales/crowdin/si.yml +++ b/modules/backlogs/config/locales/crowdin/si.yml @@ -25,6 +25,8 @@ si: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "තත්ත්වය" story_points: "කතන්දර කරුණු" @@ -43,128 +45,97 @@ si: attributes: task_type: "Task type" backlogs: - add_new_story: "නව කතාව" any: "ඔනෑම" - backlog_settings: "බැක් ලොග් සැකසුම්" - burndown_graph: "පිළිස්සෙන ප්රස්තාරය" - card_paper_size: "කාඩ් මුද්රණය සඳහා කඩදාසි ප්රමාණය" - chart_options: "සටහන විකල්ප" - close: "වසන්න" - column_width: "තීරුව පළල:" - date: "දවස" + column_width: "Column width" definition_of_done: "සිදු කරන ලද අර්ථ දැක්වීම" - generating_chart: "ප්රස්තාරය ජනනය..." - hours: "පැය" impediment: "බාධාවන්" label_versions_default_fold_state: "නවනු අනුවාද පෙන්වන්න" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." work_package_is_closed: "වැඩ පැකේජය සිදු කරනු ලබන්නේ කවදාද" label_is_done_status: "තත්ත්වය %{status_name} යන්නෙන් අදහස් කරන්නේ" - no_burndown_data: "කිසිදු පිළිස්සීමක් දත්ත ලබා ගත හැකි. එය ස්ප්රින්ට් ආරම්භක හා අවසන් දින නියම කිරීම අවශ්ය වේ." - points: "ලකුණු" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "තනතුරු නැවත ගොඩනැඟිය නොහැක." positions_rebuilt_successfully: "තනතුරු සාර්ථකව නැවත ගොඩනඟා ඇත." - properties: "ගුණාංග" rebuild: "නැවත" rebuild_positions: "තනතුරු නැවත" remaining_hours: "Remaining work" - remaining_hours_ideal: "Remaining work (ideal)" show_burndown_chart: "බර්න්ඩවුන් සටහන" story: "කතාව" - story_points: "කතන්දර කරුණු" - story_points_ideal: "කතාව ලකුණු (කදිම)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "කාර්යය" task_color: "කාර්ය වර්ණ" unassigned: "නොපවරා ඇත" user_preference: header_backlogs: "Backlogs module" button_update_backlogs: "Update backlogs module" - x_more: "%{count} තවත්..." - backlogs_active: "ක්රියාකාරී" - backlogs_any: "ඔනෑම" - backlogs_inactive: "ව්යාපෘතිය කිසිදු ක්රියාකාරකමක් පෙන්වයි" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "ලකුණු ඉහළ/පහළ පිළිස්සීම" backlogs_product_backlog: "නිෂ්පාදන බැක්ලොග්" - backlogs_product_backlog_is_empty: "නිෂ්පාදන බැක්ලොග් හිස්" - backlogs_product_backlog_unsized: "නිෂ්පාදන බැක්ලොගයේ ඉහළට ප්රමාණයේ කතන්දර ඇත" - backlogs_sizing_inconsistent: "කතන්දර ප්රමාණ ඔවුන්ගේ ඇස්තමේන්තු වලට වඩා වෙනස් වේ" - backlogs_sprint_notes_missing: "රෙට්රොස්පෙක්ටිව්/සමාලෝචන සටහන් නොමැතිව වසා දැමූ ස්ප්රින්ට්" - backlogs_sprint_unestimated: "ගණන් බලා නැති කථා සහිත සංවෘත හෝ ක්රියාකාරී ස්ප්රින්ට්" - backlogs_sprint_unsized: "ව්යාපෘතියේ ක්රියාකාරී හෝ මෑතකදී වසා දැමූ උල්පත් පිළිබඳ කථා ඇත" - backlogs_sprints: "ස්ප්රින්ට්" backlogs_story: "කතාව" backlogs_story_type: "කතන්දර වර්ග" backlogs_task: "කාර්යය" backlogs_task_type: "කාර්ය වර්ගය" - backlogs_velocity_missing: "මෙම ව්යාපෘතිය සඳහා ප්රවේගය ගණනය කළ නොහැකිය" - backlogs_velocity_varies: "ප්රවේගය sprints කට සැලකිය යුතු වෙනස්" backlogs_wiki_template: "ස්ප්රින්ට් විකි පිටුව සඳහා සැකිල්ල" - backlogs_empty_title: "කිසිදු අනුවාදයක් backlogs භාවිතා කිරීමට අර්ථ දක්වා නැත" - backlogs_empty_action_text: "backlogs සමඟ ආරම්භ කිරීම සඳහා, නව අනුවාදයක් නිර්මාණය කර එය backlogs තීරුවකට පවරන්න." - button_edit_wiki: "විකි පිටුව සංස්කරණය කරන්න" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "පහත සඳහන් දෝෂ වලට මුහුණ දීමට සිදු විය:" - error_intro_singular: "පහත දැක්වෙන දෝෂය ඇති විය:" - error_outro: "නැවත ඉදිරිපත් කිරීමට පෙර ඉහත දෝෂ නිවැරදි කරන්න." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "කදිම" - inclusion: "ලැයිස්තුවට ඇතුළත් නොවේ" - label_back_to_project: "ව්‍යාපෘතියේ පිටුවට ආපසු" - label_backlog: "බැක්ලොග්" label_backlogs: "බැක්ලොග්ස්" label_backlogs_unconfigured: "ඔබ තවමත් Backlogs වින්යාස කර නැත. කරුණාකර යන්න %{administration} > %{plugins}, ඉන්පසු මෙම ප්ලගිනය සඳහා %{configure} සබැඳිය ක්ලික් කරන්න. ඔබ ක්ෂේත්ර සකස් කළ පසු, මෙවලම භාවිතා කිරීම ආරම්භ කිරීමට මෙම පිටුවට නැවත එන්න." label_blocks_ids: "අවහිර කරන ලද වැඩ පැකේජ වල IDS" - label_burndown: "බර්න්ඩවුන්" label_column_in_backlog: "පසුබිම තුළ තීරුව" - label_hours: "පැය" - label_work_package_hierarchy: "වැඩ පැකේජය ධූරාවලිය" - label_master_backlog: "මාස්ටර් බැක්ලොග්" - label_not_prioritized: "ප්රමුඛත්වය ලබා නැත" - label_points: "ලකුණු" label_points_burn_down: "පහළට" label_points_burn_up: "ඉහළට" - label_product_backlog: "නිෂ්පාදන බැක්ලොග්" - label_select_all: "සියල්ල තෝරන්න" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "ස්ප්රින්ට් බැක්ලොග්" - label_sprint_cards: "අපනයන කාඩ්පත්" label_sprint_impediments: "ස්ප්රින්ට් බාධාවන්" - label_sprint_name: "ස්ප්රින්ට් \"%{name}”" - label_sprint_velocity: "Velocity %{velocity}, based on %{sprints} sprints with an average %{days} days" - label_stories: "කතන්දර" - label_stories_tasks: "මහල් හා කාර්යයන්" label_task_board: "කාර්ය මණ්ඩලය" - label_version_setting: "අනුවාද" - label_version: 'අනුවාදය' - label_webcal: "වෙබ්කල් පෝෂණය" - label_wiki: "විකි" permission_view_master_backlog: "ස්වාමියා බැක්ලොග් දැක්ම" permission_view_taskboards: "දැක්ම කාර්ය මණ්ඩල" permission_select_done_statuses: "Select done statuses" permission_update_sprints: "ස්ප්රින්ට් යාවත්කාලීන" - points_accepted: "පිළිගත් ලකුණු" - points_committed: "කැපවූ ලකුණු" - points_resolved: "විසඳුණු ලකුණු" - points_to_accept: "ලකුණු පිළිගත්තේ නැත" - points_to_resolve: "විසඳී නැති කරුණු" project_module_backlogs: "බැක්ලොග්ස්" - rb_label_copy_tasks: "වැඩ පැකේජ පිටපත් කරන්න" - rb_label_copy_tasks_all: "සියලු" - rb_label_copy_tasks_none: "කිසිවක් නැත" - rb_label_copy_tasks_open: "විවෘත" - rb_label_link_to_original: "මුල් කතාවට සබැඳිය ඇතුළත් කරන්න" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "remaining work" - required_burn_rate_hours: "අවශ්ය පිළිස්සීම් අනුපාතය (පැය)" - required_burn_rate_points: "අවශ්ය පිළිස්සීම් අනුපාතය (ලකුණු)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "පසුබිම තුළ තීරුව" version_settings_display_option_left: "වම්" version_settings_display_option_none: "කිසිවක් නැත" diff --git a/modules/backlogs/config/locales/crowdin/sk.yml b/modules/backlogs/config/locales/crowdin/sk.yml index ee26eb2b7a6..1627cf6632e 100644 --- a/modules/backlogs/config/locales/crowdin/sk.yml +++ b/modules/backlogs/config/locales/crowdin/sk.yml @@ -25,6 +25,8 @@ sk: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Pozícia" story_points: "História bodov" @@ -43,128 +45,101 @@ sk: attributes: task_type: "Task type" backlogs: - add_new_story: "Nový príbeh" any: "akékoľvek" - backlog_settings: "Backlogs settings" - burndown_graph: "Burndown Graf" - card_paper_size: "Veľkosť papiera pre tlač kariet" - chart_options: "Možnosti grafu" - close: "Zatvoriť" - column_width: "Šírka stĺpca:" - date: "Deň" + column_width: "Column width" definition_of_done: "Definícia pojmu Hotovo" - generating_chart: "Generovanie grafu..." - hours: "Hodín" impediment: "Prekážka" label_versions_default_fold_state: "Zobrazenie zložených verzií" caption_versions_default_fold_state: "Verzie sa pri prezeraní nevybavených dokumentov nebudú predvolene rozbaľovať. Každú z nich je potrebné rozbaliť manuálne." work_package_is_closed: "Pracovný balík je hotový, keď" label_is_done_status: "Status %{status_name} znamená dokončené" - no_burndown_data: "Nie sú k dispozícii údaje o postupe prác. Je potrebné mať nastavené dátumy začiatku a konca šprintov." - points: "Body" + points_label: + one: "point" + few: "points" + many: "points" + other: "points" positions_could_not_be_rebuilt: "Pozície nemohli byť prestavané." positions_rebuilt_successfully: "Pozície úspešne prestavané." - properties: "Vlastnosti" rebuild: "Obnoviť" rebuild_positions: "Obnoviť pozície" remaining_hours: "Zostávajúca práca" - remaining_hours_ideal: "Zostávajúca práca (ideálne)" show_burndown_chart: "Burndown Graf" story: "Príbeh" - story_points: "História bodov" - story_points_ideal: "Story Points (ideal)" + story_points: + one: "%{count} story point" + few: "%{count} story points" + many: "%{count} story points" + other: "%{count} story points" task: "Úloha" task_color: "Farba úlohy" unassigned: "Nepriradené" user_preference: header_backlogs: "Modul nevyriešených úloh" button_update_backlogs: "Aktualizácia modulu nevybavených úloh" - x_more: "ďalších %{count}..." - backlogs_active: "aktívne" - backlogs_any: "akékoľvek" - backlogs_inactive: "Projekt nevykazuje žiadne činnosti" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Burnup/-down body" backlogs_product_backlog: "Nevyriešený produkt" - backlogs_product_backlog_is_empty: "Product backlog is empty" - backlogs_product_backlog_unsized: "The top of the product backlog has unsized stories" - backlogs_sizing_inconsistent: "Veľkosti príbehov sa líšia od ich odhadov" - backlogs_sprint_notes_missing: "Closed sprints without retrospective/review notes" - backlogs_sprint_unestimated: "Closed or active sprints with unestimated stories" - backlogs_sprint_unsized: "Project has stories on active or recently closed sprints that were not sized" - backlogs_sprints: "Šprinty" backlogs_story: "Príbeh" backlogs_story_type: "Story types" backlogs_task: "Úloha" backlogs_task_type: "Typ úlohy" - backlogs_velocity_missing: "Pre tento projekt nebolo možné vypočítať žiadnu rýchlosť" - backlogs_velocity_varies: "Velocity varies significantly over sprints" backlogs_wiki_template: "Template for sprint wiki page" - backlogs_empty_title: "Nie sú definované žiadne verzie pre rozpracovanosť" - backlogs_empty_action_text: "Ak chcete používať rozpracovanosť, vytvorte novú verziu a priraďte ju do stĺpca rozpracovanosti." - button_edit_wiki: "Upraviť stránku wiki" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "The following errors were encountered:" - error_intro_singular: "The following error was encountered:" - error_outro: "Please correct the above errors before submitting again." - event_sprint_description: "%{summary}: %{url}%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ideálne" - inclusion: "nie je zahrnuté v zozname" - label_back_to_project: "Späť na stránku projektu" - label_backlog: "Backlog" label_backlogs: "Backlogs" label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "Identifikátory blokovaných pracovných balíkov" - label_burndown: "Burndown" label_column_in_backlog: "Column in backlog" - label_hours: "hodiny" - label_work_package_hierarchy: "Work package Hierarchy" - label_master_backlog: "Master Backlog" - label_not_prioritized: "bez priority" - label_points: "body" label_points_burn_down: "Nadol" label_points_burn_up: "Nahor" - label_product_backlog: "product backlog" - label_select_all: "Vybrať všetko" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "sprint backlog" - label_sprint_cards: "Export karty" label_sprint_impediments: "Sprint Impediments" - label_sprint_name: "Šprint \"%{name}\"" - label_sprint_velocity: "Velocity %{velocity}, based on %{sprints} sprints with an average %{days} days" - label_stories: "Príbehy" - label_stories_tasks: "Stories/Tasks" label_task_board: "Task board" - label_version_setting: "Verzie" - label_version: 'Verzia' - label_webcal: "Webcal Feed" - label_wiki: "Wiki" permission_view_master_backlog: "View master backlog" permission_view_taskboards: "View taskboards" permission_select_done_statuses: "Select done statuses" permission_update_sprints: "Aktualizácia šprinty" - points_accepted: "body prijaté" - points_committed: "body odovzdané" - points_resolved: "body vyriešené" - points_to_accept: "body neprijaté" - points_to_resolve: "body nevyriešené" project_module_backlogs: "Backlogs" - rb_label_copy_tasks: "Skopírovať pracovné balíčky" - rb_label_copy_tasks_all: "Všetky" - rb_label_copy_tasks_none: "Žiadne" - rb_label_copy_tasks_open: "Otvorené" - rb_label_link_to_original: "Obsahuje odkaz na pôvodný príbeh" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "remaining work" - required_burn_rate_hours: "potrebné napáliť hodnotiť (hodiny)" - required_burn_rate_points: "požadované horenia (body)" - todo_work_package_description: "%{summary}: %{url}%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Column in backlog" version_settings_display_option_left: "vľavo" version_settings_display_option_none: "žiadne" diff --git a/modules/backlogs/config/locales/crowdin/sl.yml b/modules/backlogs/config/locales/crowdin/sl.yml index b827b1576bf..c1ce0980904 100644 --- a/modules/backlogs/config/locales/crowdin/sl.yml +++ b/modules/backlogs/config/locales/crowdin/sl.yml @@ -25,6 +25,8 @@ sl: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Položaj" story_points: "Točke v zgodbi" @@ -43,128 +45,101 @@ sl: attributes: task_type: "Task type" backlogs: - add_new_story: "Nova zgodba" any: "katerikoli" - backlog_settings: "Nastavitve opravil na čakanju" - burndown_graph: "Graf burndown" - card_paper_size: "Velikost papirja za tiskanje kartic" - chart_options: "Možnosti grafikona" - close: "Zapri" - column_width: "Širina stolpca" - date: "Dan" + column_width: "Column width" definition_of_done: "Opredelitev opravljenega" - generating_chart: "Generiranje grafa" - hours: "Ure" impediment: "Ovira" label_versions_default_fold_state: "Pokaži različice zložene" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." work_package_is_closed: "Delovni paket je končan, ko" label_is_done_status: "Status %{status_name} pomeni zaključeno" - no_burndown_data: "Podatkov o zagonu ni na voljo. Potrebno je določiti začetne in končne datume teka." - points: "Točke" + points_label: + one: "point" + two: "points" + few: "points" + other: "points" positions_could_not_be_rebuilt: "Položajev ni bilo mogoče obnoviti" positions_rebuilt_successfully: "Položaj je uspešno spremenjen!" - properties: "Lastnosti" rebuild: "Obnovi" rebuild_positions: "Obnovi položaj" remaining_hours: "Remaining work" - remaining_hours_ideal: "Remaining work (ideal)" show_burndown_chart: "Graf burndown" story: "Zgodba" - story_points: "Točke v zgodbi" - story_points_ideal: "Točke v zgodbi" + story_points: + one: "%{count} story point" + two: "%{count} story points" + few: "%{count} story points" + other: "%{count} story points" task: "Opravilo" task_color: "Barva naloge" unassigned: "Nedodeljeno" user_preference: header_backlogs: "Backlogs module" button_update_backlogs: "Update backlogs module" - x_more: "%{count} več..." - backlogs_active: "aktiven" - backlogs_any: "katerikoli" - backlogs_inactive: "Na projektu ni aktivnosti" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Točke gorijo gor/dol" backlogs_product_backlog: "Zaostanki produkta" - backlogs_product_backlog_is_empty: "Ni zaostankov produkta" - backlogs_product_backlog_unsized: "Vrh zaostankov produkta ima nerazširjene zgodbe" - backlogs_sizing_inconsistent: "Velikosti zgodb se razlikujejo glede na njihove ocene" - backlogs_sprint_notes_missing: "Zaprti teki brez retrospektivnih / preglednih opomb\n" - backlogs_sprint_unestimated: "Zaprti ali aktivni teki z neocenjenimi zgodbami" - backlogs_sprint_unsized: "Projekt ima zgodbe o aktivnih ali nedavno zaprtih tekih, ki niso bili zajeti" - backlogs_sprints: "Teki" backlogs_story: "Zgodba" backlogs_story_type: "Tipi zgodb" backlogs_task: "Opravilo" backlogs_task_type: "Vrsta opravila" - backlogs_velocity_missing: "Za ta projekt ni mogoče izračunati hitrosti" - backlogs_velocity_varies: "Hitrost se med sprinti občutno razlikuje\n" backlogs_wiki_template: "Predloga za stran wiki teka" - backlogs_empty_title: "Za zaostanke ni definirana nobena različica" - backlogs_empty_action_text: "Če želite začeti z vtičnikom za zaostanke, ustvarite novo različico in jo dodelite stolpcu zaostanki." - button_edit_wiki: "Uredi wiki stran" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "Prišlo je do naslednjih napak:" - error_intro_singular: "Prišlo je do naslednje napake:" - error_outro: "Odpravite napake pred ponovno potrditvijo" - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "Idealno" - inclusion: "ni vključen na seznamu" - label_back_to_project: "Nazaj na stran projekta" - label_backlog: "Zaostanek" label_backlogs: "Zaostanki" label_backlogs_unconfigured: "Zaostankov še niste konfigurirali. Pojdite na %{administration} > %{plugins}, nato kliknite povezavo %{configure} za ta vtičnik. Ko nastavite polja, se vrnite na to stran, da začnete uporabljati orodje." label_blocks_ids: "ID blokiranih delovnih paketov" - label_burndown: "Okvara" label_column_in_backlog: "Stolpec v zaostanku" - label_hours: "Ur" - label_work_package_hierarchy: "Hierarhija delovnega paketa" - label_master_backlog: "Glavni zaostanek" - label_not_prioritized: "ni prednostno" - label_points: "Točke" label_points_burn_down: "Navzdol" label_points_burn_up: "Navzgor" - label_product_backlog: "zaostanek produkta" - label_select_all: "Izberi vse" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "zaostanek sprinta" - label_sprint_cards: "Izvozi" label_sprint_impediments: "Motnje sprinta" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Hitrost %{velocity}, ki temelji na %{sprints} sprintih s povprečno %{days} dni" - label_stories: "Zgodbe" - label_stories_tasks: "Zgodbe/naloge" label_task_board: "Tabla opravil" - label_version_setting: "Različice" - label_version: 'Različica' - label_webcal: "Spletni vir" - label_wiki: "Wiki" permission_view_master_backlog: "glavni zaostanek prejšnje poizvedbe" permission_view_taskboards: "Prikaz delovne table" permission_select_done_statuses: "Označi statuse narejeno" permission_update_sprints: "Posodobi šprinte" - points_accepted: "sprejete točke" - points_committed: "Oddane točke" - points_resolved: "Razrešene točke" - points_to_accept: "točke niso sprejete" - points_to_resolve: "točke niso razrešene" project_module_backlogs: "Zaostanki" - rb_label_copy_tasks: "Kopiraj delovne pakete" - rb_label_copy_tasks_all: "Vse" - rb_label_copy_tasks_none: "Brez" - rb_label_copy_tasks_open: "Odpri" - rb_label_link_to_original: "Vključi povezavo do izvirne zgodbe" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "remaining work" - required_burn_rate_hours: "potrebna hitrost porabe (ure)" - required_burn_rate_points: "potrebna hitrost porabe (točke)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Stolpec v zaostanku" version_settings_display_option_left: "levo" version_settings_display_option_none: "brez" diff --git a/modules/backlogs/config/locales/crowdin/sr.yml b/modules/backlogs/config/locales/crowdin/sr.yml index f99112b71e6..9726767397b 100644 --- a/modules/backlogs/config/locales/crowdin/sr.yml +++ b/modules/backlogs/config/locales/crowdin/sr.yml @@ -25,6 +25,8 @@ sr: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Pozicija" story_points: "Poeni Priče" @@ -43,128 +45,99 @@ sr: attributes: task_type: "Task type" backlogs: - add_new_story: "Nova Priča" any: "bilo koji" - backlog_settings: "Podešavanja backlog-a" - burndown_graph: "Burndown grafik" - card_paper_size: "Format papira za štampanje kartica" - chart_options: "Opcije grafikona" - close: "Zatvori" - column_width: "Širina kolone:" - date: "Dan" + column_width: "Column width" definition_of_done: "Definicija završetka" - generating_chart: "Generisanje Grafika..." - hours: "Časovi" impediment: "Smetnja" label_versions_default_fold_state: "Prikaži verzije skupljene" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." work_package_is_closed: "Radni paket je završen, kada" label_is_done_status: "Status %{status_name} znači završen" - no_burndown_data: "Nema dostupnih burndown podataka. Potrebno je zadati datum početka i kraja sprint-a." - points: "Poeni" + points_label: + one: "point" + few: "points" + other: "points" positions_could_not_be_rebuilt: "Pozicije ne mogu biti rekonstruisane." positions_rebuilt_successfully: "Rekonstrukcija pozicija je uspešna." - properties: "Svojstva" rebuild: "Rekonstrukcija" rebuild_positions: "Rekonstruiši pozicije" remaining_hours: "Remaining work" - remaining_hours_ideal: "Remaining work (ideal)" show_burndown_chart: "Burndown grafik" story: "Priča" - story_points: "Poeni Priče" - story_points_ideal: "Poeni Priče (idealno)" + story_points: + one: "%{count} story point" + few: "%{count} story points" + other: "%{count} story points" task: "Zadatak" task_color: "Boja zadatka" unassigned: "Nedodeljen" user_preference: header_backlogs: "Backlogs module" button_update_backlogs: "Update backlogs module" - x_more: "%{count} više..." - backlogs_active: "aktivno" - backlogs_any: "bilo koji" - backlogs_inactive: "Projekat ne pokazuje aktivnosti" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "burn up/down poeni" backlogs_product_backlog: "Backlog proizvoda" - backlogs_product_backlog_is_empty: "Backlog proizvoda je prazan" - backlogs_product_backlog_unsized: "Vrh backlog-a proizvoda ima nedefinisane priče" - backlogs_sizing_inconsistent: "Veličine priča variraju u odnosu na njihove procene" - backlogs_sprint_notes_missing: "Closed sprints without retrospective/review notes" - backlogs_sprint_unestimated: "Closed or active sprints with unestimated stories" - backlogs_sprint_unsized: "Project has stories on active or recently closed sprints that were not sized" - backlogs_sprints: "Sprints" backlogs_story: "Priča" backlogs_story_type: "Story types" backlogs_task: "Zadatak" backlogs_task_type: "Task type" - backlogs_velocity_missing: "No velocity could be calculated for this project" - backlogs_velocity_varies: "Velocity varies significantly over sprints" backlogs_wiki_template: "Template for sprint wiki page" - backlogs_empty_title: "No versions are defined to be used in backlogs" - backlogs_empty_action_text: "To get started with backlogs, create a new version and assign it to a backlogs column." - button_edit_wiki: "Edit wiki page" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "The following errors were encountered:" - error_intro_singular: "The following error was encountered:" - error_outro: "Please correct the above errors before submitting again." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ideal" - inclusion: "is not included in the list" - label_back_to_project: "Back to project page" - label_backlog: "Backlog" label_backlogs: "Backlogs" label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "IDs of blocked work packages" - label_burndown: "Burndown" label_column_in_backlog: "Column in backlog" - label_hours: "hours" - label_work_package_hierarchy: "Work package Hierarchy" - label_master_backlog: "Master Backlog" - label_not_prioritized: "not prioritized" - label_points: "points" label_points_burn_down: "Down" label_points_burn_up: "Up" - label_product_backlog: "product backlog" - label_select_all: "Select all" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "sprint backlog" - label_sprint_cards: "Export cards" label_sprint_impediments: "Sprint Impediments" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Velocity %{velocity}, based on %{sprints} sprints with an average %{days} days" - label_stories: "Stories" - label_stories_tasks: "Stories/Tasks" label_task_board: "Task board" - label_version_setting: "Versions" - label_version: 'Verzija' - label_webcal: "Webcal Feed" - label_wiki: "Wiki" permission_view_master_backlog: "View master backlog" permission_view_taskboards: "View taskboards" permission_select_done_statuses: "Select done statuses" permission_update_sprints: "Update sprints" - points_accepted: "points accepted" - points_committed: "points committed" - points_resolved: "points resolved" - points_to_accept: "points not accepted" - points_to_resolve: "points not resolved" project_module_backlogs: "Backlogs" - rb_label_copy_tasks: "Copy work packages" - rb_label_copy_tasks_all: "All" - rb_label_copy_tasks_none: "None" - rb_label_copy_tasks_open: "Open" - rb_label_link_to_original: "Include link to original story" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "remaining work" - required_burn_rate_hours: "required burn rate (hours)" - required_burn_rate_points: "required burn rate (points)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Column in backlog" version_settings_display_option_left: "left" version_settings_display_option_none: "none" diff --git a/modules/backlogs/config/locales/crowdin/sv.yml b/modules/backlogs/config/locales/crowdin/sv.yml index 203c6c0464f..072966d4fe3 100644 --- a/modules/backlogs/config/locales/crowdin/sv.yml +++ b/modules/backlogs/config/locales/crowdin/sv.yml @@ -25,6 +25,8 @@ sv: description: "Modulen lägger till funktioner som gör det möjligt för agila team att arbeta med OpenProject i Scrum-projekt." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Position" story_points: "Berättelsepoäng" @@ -43,128 +45,97 @@ sv: attributes: task_type: "Task type" backlogs: - add_new_story: "Ny berättelse" any: "någon" - backlog_settings: "Inställningar för backlog" - burndown_graph: "Burndown Graph" - card_paper_size: "Pappersstorlek för kortutskrift" - chart_options: "Alternativ för diagram" - close: "Stäng" - column_width: "Kolumnens bredd:" - date: "Dag" + column_width: "Column width" definition_of_done: "Definition av klart" - generating_chart: "Genererar diagram..." - hours: "Timmar" impediment: "Hinder" label_versions_default_fold_state: "Visa ihopfällda versioner" caption_versions_default_fold_state: "Versioner kommer inte att utökas som standard när du visar backloggar. Var och en måste utökas manuellt." work_package_is_closed: "Arbetspaket är klart, när" label_is_done_status: "Status %{status_name} innebär klart" - no_burndown_data: "Ingen Burndown data tillgänglig. Start- och slutdatum för sprint måste definieras." - points: "Poäng" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "Positioner kunde inte beräknas om." positions_rebuilt_successfully: "Positioner beräknades om." - properties: "Egenskaper" rebuild: "Rekonstruera" rebuild_positions: "Rekonstruera positioner" remaining_hours: "Återstående arbete" - remaining_hours_ideal: "Planerat återstående arbete" show_burndown_chart: "Burndown-diagram" story: "Berättelse" - story_points: "Berättelsepoäng" - story_points_ideal: "Berättelsepoäng (ideala)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "Aktivitet" task_color: "Aktivitetsfärg" unassigned: "Ej tilldelad" user_preference: header_backlogs: "Backlogmodul" button_update_backlogs: "Uppdatera backlogmodul" - x_more: "%{count} mer..." - backlogs_active: "aktiv" - backlogs_any: "någon" - backlogs_inactive: "Projektet visar ingen aktivitet" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Burn up/-down poäng" backlogs_product_backlog: "Produktbacklog" - backlogs_product_backlog_is_empty: "Produktbacklogen är tom" - backlogs_product_backlog_unsized: "Toppen av produktbackloggen har berättelser utan storlek" - backlogs_sizing_inconsistent: "Berättelsernas storlekar avviker ifrån sina uppskattningar" - backlogs_sprint_notes_missing: "Stängda sprinter utan retrospektiv/granskningsanteckningar" - backlogs_sprint_unestimated: "Stängda eller aktiva sprintar med berättelser utan storlek" - backlogs_sprint_unsized: "Projektet har aktiva eller nyligen stängda sprintar med berättelser som saknar storlek" - backlogs_sprints: "Sprinter" backlogs_story: "Berättelse" backlogs_story_type: "Berättelsetyp" backlogs_task: "Uppgift" backlogs_task_type: "Aktivitetstyp" - backlogs_velocity_missing: "Ingen hastighet kunde beräknas för detta projekt" - backlogs_velocity_varies: "Hastigheten varierar betydligt över sprinter" backlogs_wiki_template: "Mall för sprint wiki-sida" - backlogs_empty_title: "Inga versioner är definierade för att användas i backlog" - backlogs_empty_action_text: "För att komma igång med backlogs, skapa en ny version och tilldela den till en kolumn för backlogs." - button_edit_wiki: "Redigera wiki-sidor" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "Följande fel uppstod:" - error_intro_singular: "Följande fel uppstod:" - error_outro: "Vänligen korrigera ovanstående fel innan du skickar in igen." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ideala" - inclusion: "ingår inte i listan" - label_back_to_project: "Tillbaka till projektsidan" - label_backlog: "Backlog" label_backlogs: "Backloggar" label_backlogs_unconfigured: "Du har inte konfigurerat backloggar ännu. Gå till %{administration} > %{plugins}, klicka på %{configure} länken för denna plugin. När du har ställt in fälten, kan du komma tillbaka till denna sida för att börja använda verktyget." label_blocks_ids: "ID:n för blockerade arbetspaket" - label_burndown: "Burndown" label_column_in_backlog: "Kolumn i backlog" - label_hours: "timmar" - label_work_package_hierarchy: "Arbetspaketshierarki" - label_master_backlog: "Master Backlog" - label_not_prioritized: "inte prioriterad" - label_points: "poäng" label_points_burn_down: "Ner" label_points_burn_up: "Upp" - label_product_backlog: "produkt backlog" - label_select_all: "Markera alla" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "sprint backlogg" - label_sprint_cards: "Exportera kort" label_sprint_impediments: "Sprint hinder" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Hastighet %{velocity}, baserat på %{sprints} sprinter med genomsnitt av %{days} dagar" - label_stories: "Berättelser" - label_stories_tasks: "Berättelser/uppgifter" label_task_board: "Aktivitetstavla" - label_version_setting: "Versioner" - label_version: 'Version' - label_webcal: "Webcal Feed" - label_wiki: "Wiki" permission_view_master_backlog: "Visa master backlog" permission_view_taskboards: "Visa aktivitetstavlor" permission_select_done_statuses: "Välj klar status" permission_update_sprints: "Uppdatera sprinter" - points_accepted: "accepterade poäng" - points_committed: "incheckade poäng" - points_resolved: "lösta poäng" - points_to_accept: "ej accepterade poäng" - points_to_resolve: "ej lösta poäng" project_module_backlogs: "Backloggar" - rb_label_copy_tasks: "Kopiera arbetspaket" - rb_label_copy_tasks_all: "Alla" - rb_label_copy_tasks_none: "Inga" - rb_label_copy_tasks_open: "Öppna" - rb_label_link_to_original: "Inkludera länk till originalberättelsen" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "återstående arbete" - required_burn_rate_hours: "tempo som krävs (timmar)" - required_burn_rate_points: "tempo som krävs (poäng)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Kolumn i backlog" version_settings_display_option_left: "vänster" version_settings_display_option_none: "inga" diff --git a/modules/backlogs/config/locales/crowdin/th.yml b/modules/backlogs/config/locales/crowdin/th.yml index c3351d1f5c1..c0cf5665688 100644 --- a/modules/backlogs/config/locales/crowdin/th.yml +++ b/modules/backlogs/config/locales/crowdin/th.yml @@ -25,6 +25,8 @@ th: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "ตำแหน่ง" story_points: "" @@ -43,128 +45,95 @@ th: attributes: task_type: "Task type" backlogs: - add_new_story: "New Story" any: "ทั้งหมด" - backlog_settings: "Backlogs settings" - burndown_graph: "Burndown Graph" - card_paper_size: "Paper size for card printing" - chart_options: "ตัวเลือกแผนภูมิ" - close: "ปิด" - column_width: "ขนาดคอลัมน์:" - date: "วันที่" + column_width: "Column width" definition_of_done: "นิยามของคำว่าเสร็จสิ้น" - generating_chart: "Generating Graph..." - hours: "ชั่วโมง" impediment: "Impediment" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." work_package_is_closed: "Work package is done, when" label_is_done_status: "Status %{status_name} means done" - no_burndown_data: "No burndown data available. It is necessary to have the sprint start- and end dates set." - points: "แต้ม" + points_label: + other: "points" positions_could_not_be_rebuilt: "Positions could not be rebuilt." positions_rebuilt_successfully: "Positions rebuilt successfully." - properties: "คุณสมบัติ" rebuild: "Rebuild" rebuild_positions: "Rebuild positions" remaining_hours: "Remaining work" - remaining_hours_ideal: "Remaining work (ideal)" show_burndown_chart: "Burndown Chart" story: "Story" - story_points: "Story Points" - story_points_ideal: "Story Points (ideal)" + story_points: + other: "%{count} story points" task: "งาน" task_color: "สีของงาน" unassigned: "ไม่ได้กำหนด" user_preference: header_backlogs: "Backlogs module" button_update_backlogs: "Update backlogs module" - x_more: "%{count} more..." - backlogs_active: "ใช้งานอยู่" - backlogs_any: "ทั้งหมด" - backlogs_inactive: "Project shows no activity" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Points burn up/down" backlogs_product_backlog: "Product backlog" - backlogs_product_backlog_is_empty: "Product backlog is empty" - backlogs_product_backlog_unsized: "The top of the product backlog has unsized stories" - backlogs_sizing_inconsistent: "Story sizes vary against their estimates" - backlogs_sprint_notes_missing: "Closed sprints without retrospective/review notes" - backlogs_sprint_unestimated: "Closed or active sprints with unestimated stories" - backlogs_sprint_unsized: "Project has stories on active or recently closed sprints that were not sized" - backlogs_sprints: "Sprints" backlogs_story: "Story" backlogs_story_type: "Story types" backlogs_task: "งาน" backlogs_task_type: "Task type" - backlogs_velocity_missing: "No velocity could be calculated for this project" - backlogs_velocity_varies: "Velocity varies significantly over sprints" backlogs_wiki_template: "Template for sprint wiki page" - backlogs_empty_title: "No versions are defined to be used in backlogs" - backlogs_empty_action_text: "To get started with backlogs, create a new version and assign it to a backlogs column." - button_edit_wiki: "Edit wiki page" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "The following errors were encountered:" - error_intro_singular: "The following error was encountered:" - error_outro: "Please correct the above errors before submitting again." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ideal" - inclusion: "ไม่ได้รวมอยู่ในรายการ" - label_back_to_project: "Back to project page" - label_backlog: "Backlog" label_backlogs: "Backlogs" label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "Id ของแพคเกจการทำงานที่ถูกบล็อก" - label_burndown: "Burndown" label_column_in_backlog: "Column in backlog" - label_hours: "ชั่วโมง" - label_work_package_hierarchy: "Work package Hierarchy" - label_master_backlog: "Master Backlog" - label_not_prioritized: "not prioritized" - label_points: "points" label_points_burn_down: "Down" label_points_burn_up: "Up" - label_product_backlog: "product backlog" - label_select_all: "Select all" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "sprint backlog" - label_sprint_cards: "Export cards" label_sprint_impediments: "Sprint Impediments" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Velocity %{velocity}, based on %{sprints} sprints with an average %{days} days" - label_stories: "Stories" - label_stories_tasks: "Stories/Tasks" label_task_board: "Task board" - label_version_setting: "เวอร์ชั่น" - label_version: 'เวอร์ชัน' - label_webcal: "Webcal Feed" - label_wiki: "วิกิ" permission_view_master_backlog: "View master backlog" permission_view_taskboards: "View taskboards" permission_select_done_statuses: "Select done statuses" permission_update_sprints: "Update sprints" - points_accepted: "points accepted" - points_committed: "points committed" - points_resolved: "points resolved" - points_to_accept: "points not accepted" - points_to_resolve: "points not resolved" project_module_backlogs: "Backlogs" - rb_label_copy_tasks: "Copy work packages" - rb_label_copy_tasks_all: "ทั้งหมด" - rb_label_copy_tasks_none: "ไม่มี" - rb_label_copy_tasks_open: "Open" - rb_label_link_to_original: "Include link to original story" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "remaining work" - required_burn_rate_hours: "required burn rate (hours)" - required_burn_rate_points: "required burn rate (points)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Column in backlog" version_settings_display_option_left: "left" version_settings_display_option_none: "ไม่มี" diff --git a/modules/backlogs/config/locales/crowdin/tr.yml b/modules/backlogs/config/locales/crowdin/tr.yml index ff07bb88f36..8229354b412 100644 --- a/modules/backlogs/config/locales/crowdin/tr.yml +++ b/modules/backlogs/config/locales/crowdin/tr.yml @@ -25,6 +25,8 @@ tr: description: "Bu modül, çevik ekiplerin Scrum projelerinde OpenProject ile çalışmasını sağlayan özellikler ekler." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Konum" story_points: "Hikaye Puanları" @@ -43,133 +45,102 @@ tr: attributes: task_type: "Görev türü" backlogs: - add_new_story: "Yeni Hikaye" any: "herhangi bir" - backlog_settings: "Biriktirme listeleri ayarları" - burndown_graph: "Burndown Grafiği" - card_paper_size: "Kart yazdırma için kağıt boyutu" - chart_options: "Grafik seçenekleri" - close: "Kapat" - column_width: "Sütun genişliği:" - date: "Gün" + column_width: "Column width" definition_of_done: "Bitti Tanımı" - generating_chart: "Grafik Oluşturma..." - hours: "Saatler" impediment: "Engel" label_versions_default_fold_state: "Katlanmış sürümleri göster" caption_versions_default_fold_state: "Birikmiş işler görüntülenirken sürümler öntanımlı olarak genişletilmeyecektir. Her birinin ayrı ayrı genişletilmesi gerekir." work_package_is_closed: "İş paketi, ne zaman" label_is_done_status: "Durum %{status_name} tamamlandı anlamına geliyor" - no_burndown_data: "Burndown verileri mevcut değil. Sprint başlangıç ve bitiş tarihlerinin ayarlanması gereklidir." - points: "Puanlar / Noktalar" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "Pozisyonlar yeniden oluşturulamadı." positions_rebuilt_successfully: "Pozisyonlar başarıyla yeniden oluşturuldu." - properties: "Özellikler" rebuild: "Yeniden inşa et" rebuild_positions: "Pozisyonu yeniden inşa et" remaining_hours: "Kalan çalışma" - remaining_hours_ideal: "Kalan çalışma (ideal)" show_burndown_chart: "Açılış tablosu" story: "Hikaye" - story_points: "Hikaye Puanları" - story_points_ideal: "Hikaye puanları (ideal)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "Görev" task_color: "Görev rengi" unassigned: "Atanmamış" user_preference: header_backlogs: "Birikmiş İşler Modülü" button_update_backlogs: "Birikmiş işler modülünü güncelle" - x_more: "%{count} daha fazla..." - backlogs_active: "etkin" - backlogs_any: "herhangi bir" - backlogs_inactive: "Proje hiçbir etkinlik göstermiyor" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Puanların azalması / artması" backlogs_product_backlog: "Ürün iş listesi" - backlogs_product_backlog_is_empty: "Ürün bekleme listesi boş" - backlogs_product_backlog_unsized: "Ürün birikiminin tepesinde boyutsuz hikayeler var" - backlogs_sizing_inconsistent: "Hikaye boyutları tahminlerine göre değişir" - backlogs_sprint_notes_missing: "Retrospektif olmayan kapalı sprintler / inceleme notları" - backlogs_sprint_unestimated: "Tahmini hikayeleri olmayan kapalı veya aktif sprintler" - backlogs_sprint_unsized: "Projenin, aktif ya da kısa süre önce kapalı olan sprintler hakkında ölü olmayan öyküleri var" - backlogs_sprints: "Sprint" backlogs_story: "Hikaye" backlogs_story_type: "Hikaye türleri" backlogs_task: "Görev" backlogs_task_type: "Görev türü" - backlogs_velocity_missing: "Bu proje için hız hesaplanamadı" - backlogs_velocity_varies: "Hız farklı hızlarda değişir" backlogs_wiki_template: "Koşu wiki sayfası için şablonu" - backlogs_empty_title: "Beklentilerde kullanılacak hiçbir sürüm tanımlanmadı" - backlogs_empty_action_text: "Birikmiş işler ile çalışmaya başlamak için yeni bir sürüm oluşturun ve bunu bir birikmiş işler sütununa atayın." - button_edit_wiki: "Wiki sayfalarını düzenlemek" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "aynı zamanda bir hikaye türü olamaz" - error_intro_plural: "Aşağıdaki hatalar algılandı:" - error_intro_singular: "Aşağıdaki hatalar algılandı:" - error_outro: "Lütfen yukarıdaki hataları tekrar göndermeden önce düzeltin." - event_sprint_description: "%{summary}:%{url}\n%{description}" - event_sprint_summary: "%{project}:%{summary}" - ideal: "ideal" - inclusion: "listeye dahil değil" - label_back_to_project: "Proje sayfasına geri dön" - label_backlog: "Biriktirme listesi" label_backlogs: "İş listeleri" label_backlogs_unconfigured: "Backlog'ları henüz yapılandırmadınız. Lütfen% %{administration}> %{plugins} adresine gidin, ardından bu eklenti için %{configure} bağlantısını tıklayın. Alanları belirledikten sonra, aracı kullanmaya başlamak için bu sayfaya geri dönün." label_blocks_ids: "Bloke iş paketleri kimlikleri" - label_burndown: "Kalan zaman" label_column_in_backlog: "Sütununda birikim var" - label_hours: "saatler" - label_work_package_hierarchy: "İş paketi hiyerarşisi" - label_master_backlog: "Ana Beklenti" - label_not_prioritized: "öncelik verilmemiş" - label_points: "puanlar" label_points_burn_down: "Aşağı" label_points_burn_up: "Yukarı" - label_product_backlog: "ürün bekleme süresi" - label_select_all: "Hepsini seç" label_select_type: "Bir tür seçin" label_select_types: "Türleri seçin" label_selected_type: "Seçili tür" label_selected_types: "Seçilen türler" - label_sprint_backlog: "sprint biriktirme listesi" - label_sprint_cards: "Kartları dışa çıkart" label_sprint_impediments: "Sprint Engelleri" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "%{sprints} ortalama %{days} güne sahip sprintler temel alınarak hız %{velocity}" - label_stories: "Hikayeler" - label_stories_tasks: "Hikayeler/Görevler" label_task_board: "Görev panosu" - label_version_setting: "Sürümler" - label_version: 'Sürüm' - label_webcal: "Webcal Yemi" - label_wiki: "Wiki" permission_view_master_backlog: "Ana bekleme günlüğünü görüntüleme" permission_view_taskboards: "Görev tahtalarını görüntüleme" permission_select_done_statuses: "Tamamlanan durumları seçiniz" permission_update_sprints: "Süratleri güncelle" - points_accepted: "kabul edilen puan" - points_committed: "taahhüt edilen puanlar" - points_resolved: "puanlar çözüldü" - points_to_accept: "puan kabul edilmedi" - points_to_resolve: "puan çözülmedi" project_module_backlogs: "İş listesi" - rb_label_copy_tasks: "İş paketlerini kopyala" - rb_label_copy_tasks_all: "Hepsi" - rb_label_copy_tasks_none: "Hiçbiri" - rb_label_copy_tasks_open: "Aç" - rb_label_link_to_original: "Orijinal hikayeye bağlantı ekle" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "kalan çalışma" - required_burn_rate_hours: "gerekli yanma oranı (saat)" - required_burn_rate_points: "gerekli yanma oranı (puan)" - todo_work_package_description: "%{summary}:%{url}\n%{description}" - todo_work_package_summary: "%{type}:%{summary}" version_settings_display_label: "Sütununda birikim var" version_settings_display_option_left: "sol" version_settings_display_option_none: "hiçbiri" version_settings_display_option_right: "sağ" setting_plugin_openproject_backlogs_story_types_caption: | - Types treated as backlog stories (e.g., Feature, User story). Must differ from task type. + Backlog hikayeleri olarak ele alınan türler (ör. Özellik, Kullanıcı hikayesi). Görev türünden farklı olmalıdır. setting_plugin_openproject_backlogs_task_type_caption: | - Type used for story tasks. Must differ from story types. + Öykü görevleri için kullanılan tür. Öykü türlerinden farklı olmalıdır. diff --git a/modules/backlogs/config/locales/crowdin/uk.yml b/modules/backlogs/config/locales/crowdin/uk.yml index 902787f021f..2a33483277a 100644 --- a/modules/backlogs/config/locales/crowdin/uk.yml +++ b/modules/backlogs/config/locales/crowdin/uk.yml @@ -25,6 +25,8 @@ uk: description: "Цей модуль додає функції, завдяки яким agile-команди можуть працювати над проєктами Scrum в OpenProject." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Позиція" story_points: "Сторі-поінти" @@ -43,128 +45,101 @@ uk: attributes: task_type: "Тип завдання" backlogs: - add_new_story: "Новий сюжет" any: "будь-який" - backlog_settings: "Налаштування Backlog" - burndown_graph: "Графік Burndown" - card_paper_size: "Розмір паперу для друку карт" - chart_options: "Параметри діаграми" - close: "Закрити" - column_width: "Ширина стовпця:" - date: "День" + column_width: "Column width" definition_of_done: "Визначення завершено" - generating_chart: "Створення графіка..." - hours: "Години" impediment: "Перешкода" label_versions_default_fold_state: "Показати складені версії" caption_versions_default_fold_state: "Версії не розгортатимуться за замовчуванням при перегляді невиконаних завдань. Кожну версію потрібно розгортати вручну." work_package_is_closed: "Робочий пакет виконується, коли" label_is_done_status: "Стан %{status_name} означає виконано" - no_burndown_data: "Немає доступних даних. Необхідно встановити початкові та кінцеві дати спринту." - points: "Пункти" + points_label: + one: "point" + few: "points" + many: "points" + other: "points" positions_could_not_be_rebuilt: "Позиції не могли бути відновлені." positions_rebuilt_successfully: "Позиції успішно відновлені." - properties: "Властивості" rebuild: "Перебудувати" rebuild_positions: "Перебудувати позиції" remaining_hours: "Залишок роботи" - remaining_hours_ideal: "Залишок роботи (ідеальний)" show_burndown_chart: "Графік Burndown" story: "Історія" - story_points: "Сторі-поінти" - story_points_ideal: "Сторі-поінти (ідеально)" + story_points: + one: "%{count} story point" + few: "%{count} story points" + many: "%{count} story points" + other: "%{count} story points" task: "Завдання" task_color: "Колір завдання" unassigned: "Не призначено" user_preference: header_backlogs: "Модуль невиконаних завдань" button_update_backlogs: "Оновити модуль невиконаних завдань" - x_more: "%{count} більше..." - backlogs_active: "активний" - backlogs_any: "усі" - backlogs_inactive: "Проект не показує активності" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Бали спалюються вгору/вниз" backlogs_product_backlog: "Backlog продукту" - backlogs_product_backlog_is_empty: "Backlog продукту порожній" - backlogs_product_backlog_unsized: "У верхній частині backlog-у продукту є нерозбиті історії" - backlogs_sizing_inconsistent: "Розміри історії відрізняються від її оцінок" - backlogs_sprint_notes_missing: "Закриті спринти без ретроспективних/оглядових приміток" - backlogs_sprint_unestimated: "Закриті або активні спринти з неоціненими історіями" - backlogs_sprint_unsized: "Проект має історії про активні або нещодавно закриті спринти, які не були розмірені" - backlogs_sprints: "Спринти" backlogs_story: "Історія" backlogs_story_type: "Тип історії" backlogs_task: "Завдання" backlogs_task_type: "Тип завдання" - backlogs_velocity_missing: "Для цього проекту швидкість не може бути розрахована" - backlogs_velocity_varies: "Швидкість істотно змінюється по спринтам" backlogs_wiki_template: "Шаблон для спринтерської вікі-сторінки" - backlogs_empty_title: "Не визначено жодних версій для використання у backlogs" - backlogs_empty_action_text: "Щоб розпочати роботу з backlogs, створіть нову версію та призначте її у стовпці backlogs." - button_edit_wiki: "Редагувати wiki-сторінку" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "не може також бути типом історії" - error_intro_plural: "Сталися такі помилки:" - error_intro_singular: "Сталася така помилка:" - error_outro: "Виправте наведені вище помилки перед повторним поданням." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ідеальний" - inclusion: "не входить до списку" - label_back_to_project: "Повернутися до сторінки проекту" - label_backlog: "Backlog" label_backlogs: "Backlogs" label_backlogs_unconfigured: "Ви ще не настроїли Backlogs. Перейдіть на %{administration}>%{plugins}, потім натисніть посилання %{configure} для цього плагіна. Після того, як ви встановите поля, поверніться на цю сторінку, щоб розпочати використання інструмента." label_blocks_ids: "Ідентифікатори заблокованих робочих пакетів" - label_burndown: "Знищувати" label_column_in_backlog: "Стовпець у backlog-у" - label_hours: "годин(и)" - label_work_package_hierarchy: "Ієрархія робочого пакету" - label_master_backlog: "Майстер Backlog-у" - label_not_prioritized: "не є пріоритетними" - label_points: "точки" label_points_burn_down: "Вниз" label_points_burn_up: "Вгору" - label_product_backlog: "backlog продукту" - label_select_all: "Вибрати все" label_select_type: "Виберіть тип" label_select_types: "Виберіть типи" label_selected_type: "Вибраний тип" label_selected_types: "Вибрані типи" - label_sprint_backlog: "backlog спринта" - label_sprint_cards: "Експортувати картки" label_sprint_impediments: "Перешкоди спринту" - label_sprint_name: "Спринт \"%{name}\"" - label_sprint_velocity: "Швидкість %{velocity}, заснована на спринтах %{sprints} із середнім %{days} днями" - label_stories: "Iсторії" - label_stories_tasks: "Історії/Завдання" label_task_board: "Дошка завдань" - label_version_setting: "Версії" - label_version: 'Версія' - label_webcal: "Веб-канал" - label_wiki: "Wiki" permission_view_master_backlog: "Перегляд головного backlog-у" permission_view_taskboards: "Перегляд панелі завдань" permission_select_done_statuses: "Виберіть завершені статуси" permission_update_sprints: "Оновлення спринту" - points_accepted: "пунктів прийнято" - points_committed: "пунктів зафіксовано" - points_resolved: "вирішено зафіксованих пунктів" - points_to_accept: "пунктів не прийнято" - points_to_resolve: "пункти не вирішені" project_module_backlogs: "Невиконані завдання" - rb_label_copy_tasks: "Копіювання робочих пакетів" - rb_label_copy_tasks_all: "Всі" - rb_label_copy_tasks_none: "Нічого" - rb_label_copy_tasks_open: "Відкрити" - rb_label_link_to_original: "Включіть посилання на оригінальну історію" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "залишок роботи" - required_burn_rate_hours: "необхідна швидкість запису (години)" - required_burn_rate_points: "необхідна швидкість запису (пункти)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Стовпець у backlog-у" version_settings_display_option_left: "зліва" version_settings_display_option_none: "нічого" diff --git a/modules/backlogs/config/locales/crowdin/uz.yml b/modules/backlogs/config/locales/crowdin/uz.yml index 52c0fe9278e..a1817f6a842 100644 --- a/modules/backlogs/config/locales/crowdin/uz.yml +++ b/modules/backlogs/config/locales/crowdin/uz.yml @@ -25,6 +25,8 @@ uz: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Position" story_points: "Story Points" @@ -43,128 +45,97 @@ uz: attributes: task_type: "Task type" backlogs: - add_new_story: "New Story" any: "any" - backlog_settings: "Backlogs settings" - burndown_graph: "Burndown Graph" - card_paper_size: "Paper size for card printing" - chart_options: "Chart options" - close: "Close" - column_width: "Column width:" - date: "Day" + column_width: "Column width" definition_of_done: "Definition of Done" - generating_chart: "Generating Graph..." - hours: "Hours" impediment: "Impediment" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." work_package_is_closed: "Work package is done, when" label_is_done_status: "Status %{status_name} means done" - no_burndown_data: "No burndown data available. It is necessary to have the sprint start- and end dates set." - points: "Points" + points_label: + one: "point" + other: "points" positions_could_not_be_rebuilt: "Positions could not be rebuilt." positions_rebuilt_successfully: "Positions rebuilt successfully." - properties: "Properties" rebuild: "Rebuild" rebuild_positions: "Rebuild positions" remaining_hours: "Remaining work" - remaining_hours_ideal: "Remaining work (ideal)" show_burndown_chart: "Burndown Chart" story: "Story" - story_points: "Story Points" - story_points_ideal: "Story Points (ideal)" + story_points: + one: "%{count} story point" + other: "%{count} story points" task: "Task" task_color: "Task color" unassigned: "Unassigned" user_preference: header_backlogs: "Backlogs module" button_update_backlogs: "Update backlogs module" - x_more: "%{count} more..." - backlogs_active: "active" - backlogs_any: "any" - backlogs_inactive: "Project shows no activity" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Points burn up/down" backlogs_product_backlog: "Product backlog" - backlogs_product_backlog_is_empty: "Product backlog is empty" - backlogs_product_backlog_unsized: "The top of the product backlog has unsized stories" - backlogs_sizing_inconsistent: "Story sizes vary against their estimates" - backlogs_sprint_notes_missing: "Closed sprints without retrospective/review notes" - backlogs_sprint_unestimated: "Closed or active sprints with unestimated stories" - backlogs_sprint_unsized: "Project has stories on active or recently closed sprints that were not sized" - backlogs_sprints: "Sprints" backlogs_story: "Story" backlogs_story_type: "Story types" backlogs_task: "Task" backlogs_task_type: "Task type" - backlogs_velocity_missing: "No velocity could be calculated for this project" - backlogs_velocity_varies: "Velocity varies significantly over sprints" backlogs_wiki_template: "Template for sprint wiki page" - backlogs_empty_title: "No versions are defined to be used in backlogs" - backlogs_empty_action_text: "To get started with backlogs, create a new version and assign it to a backlogs column." - button_edit_wiki: "Edit wiki page" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "can not also be a story type" - error_intro_plural: "The following errors were encountered:" - error_intro_singular: "The following error was encountered:" - error_outro: "Please correct the above errors before submitting again." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "ideal" - inclusion: "is not included in the list" - label_back_to_project: "Back to project page" - label_backlog: "Backlog" label_backlogs: "Backlogs" label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "IDs of blocked work packages" - label_burndown: "Burndown" label_column_in_backlog: "Column in backlog" - label_hours: "hours" - label_work_package_hierarchy: "Work package Hierarchy" - label_master_backlog: "Master Backlog" - label_not_prioritized: "not prioritized" - label_points: "points" label_points_burn_down: "Down" label_points_burn_up: "Up" - label_product_backlog: "product backlog" - label_select_all: "Select all" label_select_type: "Select a type" label_select_types: "Select types" label_selected_type: "Selected type" label_selected_types: "Selected types" - label_sprint_backlog: "sprint backlog" - label_sprint_cards: "Export cards" label_sprint_impediments: "Sprint Impediments" - label_sprint_name: "Sprint \"%{name}\"" - label_sprint_velocity: "Velocity %{velocity}, based on %{sprints} sprints with an average %{days} days" - label_stories: "Stories" - label_stories_tasks: "Stories/Tasks" label_task_board: "Task board" - label_version_setting: "Versions" - label_version: 'Version' - label_webcal: "Webcal Feed" - label_wiki: "Wiki" permission_view_master_backlog: "View master backlog" permission_view_taskboards: "View taskboards" permission_select_done_statuses: "Select done statuses" permission_update_sprints: "Update sprints" - points_accepted: "points accepted" - points_committed: "points committed" - points_resolved: "points resolved" - points_to_accept: "points not accepted" - points_to_resolve: "points not resolved" project_module_backlogs: "Backlogs" - rb_label_copy_tasks: "Copy work packages" - rb_label_copy_tasks_all: "All" - rb_label_copy_tasks_none: "None" - rb_label_copy_tasks_open: "Open" - rb_label_link_to_original: "Include link to original story" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "remaining work" - required_burn_rate_hours: "required burn rate (hours)" - required_burn_rate_points: "required burn rate (points)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Column in backlog" version_settings_display_option_left: "left" version_settings_display_option_none: "none" diff --git a/modules/backlogs/config/locales/crowdin/vi.yml b/modules/backlogs/config/locales/crowdin/vi.yml index a5c209cbc4e..c6f646d6356 100644 --- a/modules/backlogs/config/locales/crowdin/vi.yml +++ b/modules/backlogs/config/locales/crowdin/vi.yml @@ -25,6 +25,8 @@ vi: description: "Mô-đun này bổ sung các tính năng cho phép các nhóm linh hoạt làm việc với OpenProject trong các dự án Scrum." activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "Vị trí" story_points: "Điểm cốt truyện" @@ -43,128 +45,95 @@ vi: attributes: task_type: "Loại nhiệm vụ" backlogs: - add_new_story: "Câu chuyện mới" any: "bất kỳ" - backlog_settings: "Lịch sử cài đặt" - burndown_graph: "Đồ thị Burndown" - card_paper_size: "Kích thước giấy in thiệp" - chart_options: "Tùy chọn biểu đồ" - close: "Đóng" - column_width: "Chiều rộng cột:" - date: "Ngày" + column_width: "Column width" definition_of_done: "Định nghĩa về Hoàn thành" - generating_chart: "Tạo đồ thị..." - hours: "Giờ" impediment: "Trở ngại" label_versions_default_fold_state: "Hiển thị các phiên bản \n" caption_versions_default_fold_state: "Các phiên bản sẽ không được mở rộng theo mặc định khi xem hồ sơ tồn đọng. Mỗi cái phải được mở rộng bằng tay." work_package_is_closed: "Gói công việc được thực hiện, khi" label_is_done_status: "Trạng thái %{status_name} nghĩa là đã xong" - no_burndown_data: "Không có sẵn dữ liệu về sự cố. Cần thiết phải ấn định ngày bắt đầu và ngày kết thúc của sprint." - points: "Điểm" + points_label: + other: "points" positions_could_not_be_rebuilt: "Không thể tạo lại vị trí." positions_rebuilt_successfully: "Tạo lại vị trí thành công." - properties: "Thuộc tính" rebuild: "Dựng lại" rebuild_positions: "Xây dựng lại vị trí" remaining_hours: "Công việc còn lại" - remaining_hours_ideal: "Công việc còn lại (lý tưởng)" show_burndown_chart: "Biểu đồ đốt cháy" story: "Câu chuyện" - story_points: "Điểm cốt truyện" - story_points_ideal: "Điểm cốt truyện (lý tưởng)" + story_points: + other: "%{count} story points" task: "Nhiệm vụ" task_color: "Màu nhiệm vụ" unassigned: "Chưa được chỉ định" user_preference: header_backlogs: "Mô-đun tồn đọng" button_update_backlogs: "Cập nhật mô-đun tồn đọng" - x_more: "%{count} thêm..." - backlogs_active: "Đang hoạt động" - backlogs_any: "bất kỳ" - backlogs_inactive: "Dự án không hiển thị hoạt động nào" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "Điểm tăng/giảm" backlogs_product_backlog: "Product backlog" - backlogs_product_backlog_is_empty: "Không có Product backlog" - backlogs_product_backlog_unsized: "Phần trên cùng của sản phẩm tồn đọng có các cốt truyện chưa được định cỡ" - backlogs_sizing_inconsistent: "Kích thước cốt truyện khác nhau so với ước tính của họ" - backlogs_sprint_notes_missing: "Chạy nước rút đã đóng mà không có ghi chú hồi tưởng/đánh giá" - backlogs_sprint_unestimated: "Chạy nước rút đã đóng hoặc đang hoạt động với các cốt truyện chưa được ước tính" - backlogs_sprint_unsized: "Dự án có các câu chuyện về các lần chạy nước rút đang hoạt động hoặc đã đóng gần đây nhưng không được định cỡ" - backlogs_sprints: "Chạy nước rút" backlogs_story: "Cốt truyện" backlogs_story_type: "Loại câu truyện tóm tắt" backlogs_task: "Nhiệm vụ" backlogs_task_type: "Loại công việc" - backlogs_velocity_missing: "Không có tốc độ hoàn thành nào được tính cho dự án này" - backlogs_velocity_varies: "Vận tốc thay đổi đáng kể qua các lần chạy nước rút" backlogs_wiki_template: "Mẫu cho trang wiki chạy nước rút" - backlogs_empty_title: "Không có phiên bản nào được xác định sẽ được sử dụng trong các hồ sơ tồn đọng" - backlogs_empty_action_text: "Để bắt đầu xử lý tồn đọng, hãy tạo một phiên bản mới và gán nó vào cột tồn đọng." - button_edit_wiki: "Sửa trang wiki" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "cũng không thể là một loại cốt truyện" - error_intro_plural: "Các lỗi đã gặp phải:" - error_intro_singular: "Đã gặp phải lỗi sau:" - error_outro: "Vui lòng sửa các lỗi trên trước khi gửi lại." - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "lý tưởng" - inclusion: "không được bao gồm trong danh sách" - label_back_to_project: "Quay lại trang dự án" - label_backlog: "Tồn đọng" label_backlogs: "tồn đọng" label_backlogs_unconfigured: "Bạn chưa cấu hình Bảng nhiệm vụ tồn đọng. Vui lòng vào %{administration} > %{plugins}, sau đó nhấp vào liên kết %{configure} cho gắn thêm này. Khi bạn đã thiết lập các trường, quay lại trang này để bắt đầu sử dụng công cụ." label_blocks_ids: "ID của các work package bị chặn" - label_burndown: "Đốt cháy" label_column_in_backlog: "Cột tồn đọng" - label_hours: "hours" - label_work_package_hierarchy: "Gói công việc" - label_master_backlog: "Tồn đọng chính" - label_not_prioritized: "không được ưu tiên" - label_points: "điểm" label_points_burn_down: "Xuống" label_points_burn_up: "lên" - label_product_backlog: "tồn đọng sản phẩm" - label_select_all: "Chọn tất cả" label_select_type: "Chọn một loại" label_select_types: "Chọn loại" label_selected_type: "Loại đã chọn" label_selected_types: "Các loại đã chọn" - label_sprint_backlog: "tồn đọng nước rút" - label_sprint_cards: "Xuất thẻ" label_sprint_impediments: "Trở ngại nước rút" - label_sprint_name: "Chạy nước rút \"%{name}\"" - label_sprint_velocity: "Vận tốc %{velocity}, dựa trên %{sprints} lần chạy nước rút với trung bình %{days} ngày" - label_stories: "Những câu chuyện" - label_stories_tasks: "Câu chuyện/Nhiệm vụ" label_task_board: "Bảng nhiệm vụ" - label_version_setting: "phiên bản" - label_version: '0886055830 ' - label_webcal: "Nguồn cấp dữ liệu Webcal" - label_wiki: "wiki" permission_view_master_backlog: "Xem tồn đọng chính" permission_view_taskboards: "Xem bảng tác vụ" permission_select_done_statuses: "Chọn trạng thái hoàn thành" permission_update_sprints: "Cập nhật các lần chạy nước rút" - points_accepted: "điểm được chấp nhận" - points_committed: "số điểm đã cam kết" - points_resolved: "điểm đã được giải quyết" - points_to_accept: "điểm không được chấp nhận" - points_to_resolve: "điểm chưa được giải quyết" project_module_backlogs: "tồn đọng" - rb_label_copy_tasks: "Sao chép work packages" - rb_label_copy_tasks_all: "Toàn bộ" - rb_label_copy_tasks_none: "không có" - rb_label_copy_tasks_open: "Mở" - rb_label_link_to_original: "Bao gồm liên kết đến câu chuyện gốc" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "công việc còn lại" - required_burn_rate_hours: "tốc độ ghi yêu cầu (giờ)" - required_burn_rate_points: "tốc độ ghi yêu cầu (điểm)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "Cột tồn đọng" version_settings_display_option_left: "trái" version_settings_display_option_none: "không" diff --git a/modules/backlogs/config/locales/crowdin/zh-CN.yml b/modules/backlogs/config/locales/crowdin/zh-CN.yml index d172ba077cd..9751f44c9de 100644 --- a/modules/backlogs/config/locales/crowdin/zh-CN.yml +++ b/modules/backlogs/config/locales/crowdin/zh-CN.yml @@ -25,6 +25,8 @@ zh-CN: description: "该模块为敏捷团队在敏捷项目中使用 OpenProject 添加了功能。" activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "位置" story_points: "故事点" @@ -43,128 +45,95 @@ zh-CN: attributes: task_type: "任务类型" backlogs: - add_new_story: "新故事" any: "任一" - backlog_settings: "待办清单设置" - burndown_graph: "燃尽图" - card_paper_size: "用于卡片打印的纸张尺寸" - chart_options: "图表选项" - close: "已关闭" - column_width: "列宽" - date: "天" + column_width: "Column width" definition_of_done: "完成的定义" - generating_chart: "正在生成图表..." - hours: "小时" impediment: "障碍" label_versions_default_fold_state: "显示已折叠的版本" caption_versions_default_fold_state: "查看积压工作时,默认情况下不会展开版本。每个版本都必须手动展开。" work_package_is_closed: "工作包已完成,当" label_is_done_status: "状态 %{status_name} 表示已完成" - no_burndown_data: "没有未完成的数据。需要设置冲刺 (Sprint) 开始日期和结束日期。" - points: "点" + points_label: + other: "points" positions_could_not_be_rebuilt: "无法重建顺序" positions_rebuilt_successfully: "已成功重建顺序" - properties: "属性" rebuild: "重建" rebuild_positions: "重建顺序" remaining_hours: "剩余工时" - remaining_hours_ideal: "剩余工时(理想)" show_burndown_chart: "燃尽图" story: "故事" - story_points: "故事点" - story_points_ideal: "故事点(理想)" + story_points: + other: "%{count} story points" task: "任务" task_color: "任务颜色" unassigned: "未指定" user_preference: header_backlogs: "积压工作模块" button_update_backlogs: "更新积压工作模块" - x_more: "还有 %{count} 个..." - backlogs_active: "激活" - backlogs_any: "任意" - backlogs_inactive: "项目显示无活动" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "点数燃尽曲线" backlogs_product_backlog: "产品待办清单" - backlogs_product_backlog_is_empty: "产品待办清单为空" - backlogs_product_backlog_unsized: "产品待办列表的顶部有未估算大小的用户故事。" - backlogs_sizing_inconsistent: "故事大小受其预估时间影响" - backlogs_sprint_notes_missing: "不包括回顾笔记的已关闭冲刺 (sprint)" - backlogs_sprint_unestimated: "带有尚未评估的故事的已关闭或活跃冲刺 (sprint)" - backlogs_sprint_unsized: "项目在尚未确定大小的活跃或最近关闭的冲刺 (sprint) 上有故事" - backlogs_sprints: "冲刺 (sprint)" backlogs_story: "故事" backlogs_story_type: "故事类型" backlogs_task: "任务" backlogs_task_type: "任务类型" - backlogs_velocity_missing: "此项目不能计算速度" - backlogs_velocity_varies: "速度在不同冲刺 (sprint) 中差异很大" backlogs_wiki_template: "冲刺 (Sprint) 的维基页面模板" - backlogs_empty_title: "未定义要用于待办清单的版本" - backlogs_empty_action_text: "要开始使用待办清单,请创建一个新版本并将其分配到待办清单列。" - button_edit_wiki: "编辑维基页面" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "不能同时为故事类型" - error_intro_plural: "发生以下错误:" - error_intro_singular: "发生以下错误:" - error_outro: "请在再次提交前更正上面的错误。" - event_sprint_description: "%{summary}:%{url}\n%{description}" - event_sprint_summary: "%{project}:%{summary}" - ideal: "理想" - inclusion: "不包含在列表中" - label_back_to_project: "返回至项目页面" - label_backlog: "待办清单" label_backlogs: "待办清单" label_backlogs_unconfigured: "您尚未配置待办清单。请转到“%{administration} > %{plugins}”,然后单击此插件的 %{configure} 链接。设置字段后,返回到此页面开始使用工具。" label_blocks_ids: "被阻止的工作包 ID" - label_burndown: "燃尽图" label_column_in_backlog: "待办清单中的列" - label_hours: "小时" - label_work_package_hierarchy: "工作包层次结构" - label_master_backlog: "主待办清单" - label_not_prioritized: "尚未安排优先顺序" - label_points: "点数" label_points_burn_down: "减少" label_points_burn_up: "增加" - label_product_backlog: "产品待办清单" - label_select_all: "全选" label_select_type: "选择类型" label_select_types: "选择类型" label_selected_type: "所选类型" label_selected_types: "所选类型" - label_sprint_backlog: "冲刺 (sprint) 待办清单" - label_sprint_cards: "导出卡片" label_sprint_impediments: "冲刺 (sprint) 障碍" - label_sprint_name: "冲刺 (sprint)“%{name}”" - label_sprint_velocity: "根据平均 %{days} 天 %{sprints} 冲刺 (sprint),速度为 %{velocity}" - label_stories: "故事" - label_stories_tasks: "故事/任务" label_task_board: "任务板" - label_version_setting: "版本" - label_version: '版本' - label_webcal: "Webcal 串流" - label_wiki: "维基" permission_view_master_backlog: "查看主待办清单" permission_view_taskboards: "查看任务板" permission_select_done_statuses: "选择完成状态" permission_update_sprints: "更新迭代" - points_accepted: "点数已接受" - points_committed: "点数已确认" - points_resolved: "点数已完成" - points_to_accept: "点数未接受" - points_to_resolve: "点数未完成" project_module_backlogs: "待办清单" - rb_label_copy_tasks: "复制工作包" - rb_label_copy_tasks_all: "全部" - rb_label_copy_tasks_none: "无" - rb_label_copy_tasks_open: "打开" - rb_label_link_to_original: "包括原始故事的链接" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "剩余工时" - required_burn_rate_hours: "所需燃尽率(小时)" - required_burn_rate_points: "所需燃尽率(点数)" - todo_work_package_description: "%{summary}:%{url}\n%{description}" - todo_work_package_summary: "%{type}:%{summary}" version_settings_display_label: "待办清单中的列" version_settings_display_option_left: "左" version_settings_display_option_none: "无" diff --git a/modules/backlogs/config/locales/crowdin/zh-TW.yml b/modules/backlogs/config/locales/crowdin/zh-TW.yml index 0de02e9396d..e2cd4ebf04e 100644 --- a/modules/backlogs/config/locales/crowdin/zh-TW.yml +++ b/modules/backlogs/config/locales/crowdin/zh-TW.yml @@ -25,6 +25,8 @@ zh-TW: description: "此模組新增了讓敏捷團隊能夠在 Scrum 專案中使用 OpenProject 的功能。" activerecord: attributes: + sprint: + duration: "Sprint duration" work_package: position: "位置" story_points: "需求重要性" @@ -43,128 +45,95 @@ zh-TW: attributes: task_type: "任務類型" backlogs: - add_new_story: "新增使用者需求" any: "任何" - backlog_settings: "待辦事項設定" - burndown_graph: "未完成圖" - card_paper_size: "卡片印製大小" - chart_options: "圖表選項" - close: "關閉" - column_width: "欄位寬度" - date: "日" + column_width: "Column width" definition_of_done: "定義" - generating_chart: "產生圖表中" - hours: "小時" impediment: "阻礙" label_versions_default_fold_state: "顯示精簡版本" caption_versions_default_fold_state: "檢視待辦清單時,版本預設不會展開,需手動逐一展開。" work_package_is_closed: "視為工作套件已完成" label_is_done_status: "狀態 %{status_name} 表示完成" - no_burndown_data: "沒有未完成的資料。起始和結束日期在進度中是必須的" - points: "點" + points_label: + other: "points" positions_could_not_be_rebuilt: "無法重建位置" positions_rebuilt_successfully: "位置重建成功" - properties: "屬性" rebuild: "重建" rebuild_positions: "重建位置" remaining_hours: "剩餘工時" - remaining_hours_ideal: "剩餘工時(ideal)" show_burndown_chart: "未完成圖" story: "使用者需求" - story_points: "需求重點" - story_points_ideal: "(理想) 需求重要性" + story_points: + other: "%{count} story points" task: "任務" task_color: "任務顏色" unassigned: "尚未指派" user_preference: header_backlogs: "待辦清單模組" button_update_backlogs: "更新待辦清單模組" - x_more: "還有 %{count} 個 ..." - backlogs_active: "啟用" - backlogs_any: "全部" - backlogs_inactive: "專案顯示沒有活動" + backlog_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in backlog" + one: "%{count} story in backlog" + other: "%{count} stories in backlog" + backlog_menu_component: + label_actions: "Backlog actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + wiki: "Wiki" + properties: "Properties" + story_component: + label_drag_story: "Move %{name}" + story_menu_component: + label_actions: "Story actions" backlogs_points_burn_direction: "重要性 增加/減少" backlogs_product_backlog: "產品待辦事項" - backlogs_product_backlog_is_empty: "產品待辦事項是空的" - backlogs_product_backlog_unsized: "最上面的產品待辦事項含有未評估大小的需求" - backlogs_sizing_inconsistent: "使用者需求的大小會影響他們的預估時間" - backlogs_sprint_notes_missing: "關閉進度前不檢視筆記" - backlogs_sprint_unestimated: "用尚未預估的使用者需求關閉或開啟進度" - backlogs_sprint_unsized: "開啟或最近關閉進度的專案中有尚未訂定大小的使用者需求" - backlogs_sprints: "進度" backlogs_story: "使用者需求" backlogs_story_type: "需求類型" backlogs_task: "任務" backlogs_task_type: "任務類型" - backlogs_velocity_missing: "專案沒有進度完成速度可以計算" - backlogs_velocity_varies: "完成速度在不同進度中差異很大" backlogs_wiki_template: "進度的 Wiki 頁面樣板" - backlogs_empty_title: "未定義要在待辦事項中使用的版本" - backlogs_empty_action_text: "要開始待辦事項, 請新增一個新版本並將其分配給待辦事項項欄位。" - button_edit_wiki: "編輯 Wiki 頁面" + backlogs_empty_title: "No versions are defined yet" + backlogs_empty_action_text: "To start using backlogs, please create a version first" + backlogs_not_configured_title: "Backlogs not configured" + backlogs_not_configured_description: "Story and task types need to be set before using this module." + backlogs_not_configured_action_text: "Configure Backlogs" + burndown: + story_points: "Story points" + story_points_ideal: "Story points (ideal)" errors: attributes: task_type: cannot_be_story_type: "不能同時是故事類型" - error_intro_plural: "發生以下的錯誤:" - error_intro_singular: "發生以下的錯誤:" - error_outro: "請在再次送出前更正前面的錯誤。" - event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{project}: %{summary}" - ideal: "理想" - inclusion: "沒有包含在這個清單" - label_back_to_project: "返回專案頁面" - label_backlog: "待辦事項" label_backlogs: "待辦事項" label_backlogs_unconfigured: "您尚未設定待辦事項。請前往 '%{administration} -> %{plugins}' 然後在 %{configure} 連結上按一下。當你設定欄位後,再返回這個頁面使用這個工具。" label_blocks_ids: "被禁止的工作套件 IDs" - label_burndown: "完成" label_column_in_backlog: "待辦事項的欄位" - label_hours: "小時" - label_work_package_hierarchy: "工作套件階層" - label_master_backlog: "主待辦事項" - label_not_prioritized: "尚未安排優先順序" - label_points: "點數" label_points_burn_down: "減少" label_points_burn_up: "增加" - label_product_backlog: "產品待辦事項" - label_select_all: "全選" label_select_type: "選擇類型" label_select_types: "選擇類型" label_selected_type: "所選類型" label_selected_types: "所選類型" - label_sprint_backlog: "進度待辦事項" - label_sprint_cards: "匯出卡片" label_sprint_impediments: "進度阻礙" - label_sprint_name: "進度 '%{name}'" - label_sprint_velocity: "根據平均 %{days} 天的進度 %{sprints} ,完成速度約 %{velocity}" - label_stories: "使用者需求" - label_stories_tasks: "需求/任務" label_task_board: "任務看板" - label_version_setting: "版本" - label_version: '版本' - label_webcal: "Webcal 串流" - label_wiki: "Wiki" permission_view_master_backlog: "檢視主待辦事項" permission_view_taskboards: "檢視任務看板" permission_select_done_statuses: "選擇完成狀態" permission_update_sprints: "更新進度" - points_accepted: "點數已被接受" - points_committed: "點數已確認" - points_resolved: "點數已完成" - points_to_accept: "點數不被接受" - points_to_resolve: "點數尚未完成" project_module_backlogs: "待辦事項" - rb_label_copy_tasks: "複製工作套件" - rb_label_copy_tasks_all: "全部" - rb_label_copy_tasks_none: "無" - rb_label_copy_tasks_open: "開啟" - rb_label_link_to_original: "包含連結到原本的使用者需求" + rb_burndown_charts: + show: + blankslate_title: "No burndown data available" + blankslate_description: "Set start and end date for the sprint to generate a burndown chart." remaining_hours: "剩餘工時" - required_burn_rate_hours: "必須的完成率 (小時)" - required_burn_rate_points: "必須的完成率 (點數)" - todo_work_package_description: "%{summary}: %{url}\n%{description}" - todo_work_package_summary: "%{type}: %{summary}" version_settings_display_label: "待辦事項的欄位" version_settings_display_option_left: "左" version_settings_display_option_none: "無" diff --git a/modules/bim/config/locales/crowdin/tr.seeders.yml b/modules/bim/config/locales/crowdin/tr.seeders.yml index cf96bd5815e..8498f2bc8c3 100644 --- a/modules/bim/config/locales/crowdin/tr.seeders.yml +++ b/modules/bim/config/locales/crowdin/tr.seeders.yml @@ -44,10 +44,10 @@ tr: item_5: name: Talep item_6: - name: Clash + name: Çatışma global_queries: item_0: - name: 'Embedded table: Children' + name: 'Gömülü tablo: Alt' type_configuration: item_0: form_configuration: @@ -73,21 +73,21 @@ tr: welcome: title: OpenProject BIM sürümüne hoş geldiniz! text: | - Checkout the demo projects to get started with some examples. + Bazı örneklerle başlamak için demo projelere göz atın. - * [(Demo) Construction project]({{opSetting:base_url}}/projects/demo-construction-project): Planning, BIM process, BCF management, and constructing, all at a glance. - * [(Demo) Planning & constructing]({{opSetting:base_url}}/projects/demo-planning-constructing-project): Classical planning and construction management. - * [(Demo) Bim project]({{opSetting:base_url}}/projects/demo-bim-project): BIM process and coordination. - * [(Demo) BCF management]({{opSetting:base_url}}/projects/demo-bcf-management-project): BCF management. + * [(Demo) İnşaat projesi]({{opSetting:base_url}}/projects/demo-construction-project): Planlama, BIM süreci, BCF yönetimi ve inşaat, hepsi bir bakışta. + * [(Demo) Planlama ve inşaat]({{opSetting:base_url}}/projects/demo-planlama-inşaat-projesi): Klasik planlama ve inşaat yönetimi. + * [(Demo) Bim projesi]({{opSetting:base_url}}/projects/demo-bim-project): BIM süreci ve koordinasyonu. + * [(Demo) BCF yönetimi]({{opSetting:base_url}}/projects/demo-bcf-management-project): BCF yönetimi. - Also, you can create a blank [new project]({{opSetting:base_url}}/projects/new). + Ayrıca, boş bir [yeni proje]({{opSetting:base_url}}/projects/new) oluşturabilirsiniz. - Never stop collaborating. With open source and open mind. + İşbirliğini asla bırakmayın. Açık kaynak ve açık fikirle. - You can change this welcome text [here]({{opSetting:base_url}}/admin/settings/general). + Bu karşılama metnini [buradan] değiştirebilirsiniz({{opSetting:base_url}}/admin/settings/general). projects: demo-construction-project: - name: "(Demo) Construction project" + name: "(Demo) Yapılandırma projesi" status_explanation: Tüm görevler ve alt projeler programa uygundur. İlgili kişiler görevlerini biliyor. Sistem tamamen kurulmuştur. description: Bu, bu demo inşaat projesinin hedeflerinin kısa bir özetidir. news: @@ -120,25 +120,25 @@ tr: options: name: Başlarken text: | - We are glad you joined! We suggest to try a few things to get started in OpenProject. + Katıldığınız için çok mutluyuz! OpenProject'e başlamak için birkaç şey denemenizi öneririz. - But before you jump right into it, you should know that this exemplary project is split up into two different projects: + Ancak hemen başlamadan önce, bu örnek projenin iki farklı projeye ayrıldığını bilmelisiniz: - 1. [Construction project]({{opSetting:base_url}}/projects/demo-planning-constructing-project): Here you will find the classical roles, some workflows and work packages for your construction project. - 2. [Creating BIM Model]({{opSetting:base_url}}/projects/demo-bim-project): This project also offers roles, workflows and work packages but especially in the BIM context. + 1. [İnşaat projesi]({{opSetting:base_url}}/projects/demo-planlama-inşaat-projesi): Burada inşaat projeniz için klasik rolleri, bazı iş akışlarını ve iş paketlerini bulacaksınız. + 2. [BIM Modeli Oluşturma]({{opSetting:base_url}}/projects/demo-bim-project): Bu proje de roller, iş akışları ve iş paketleri sunar, ancak özellikle BIM bağlamında. - _Try the following steps:_ + Aşağıdaki adımları deneyin:_ - 1. _Invite new members to your project_: → Go to [Members]({{opSetting:base_url}}/projects/demo-construction-project/members) in the project navigation. - 2. _View the work in your projects_: → Go to [Work packages]({{opSetting:base_url}}/projects/demo-construction-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22bcfIssueAssociated%22%2C%22o%22%3A%22%3D%22%2C%22v%22%3A%5B%22f%22%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D) in the project navigation. - 3. _Create a new work package_: → Go to [Work packages → Create]({{opSetting:base_url}}/projects/demo-construction-project/work_packages/new?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22bcfIssueAssociated%22%2C%22o%22%3A%22%3D%22%2C%22v%22%3A%5B%22f%22%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D&type=11). - 4. _Create and update a Gantt chart_: → Go to [Gantt chart]({{opSetting:base_url}}/projects/demo-construction-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22assignee%22%2C%22responsible%22%5D%2C%22tv%22%3Atrue%2C%22tzl%22%3A%22weeks%22%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D) in the project navigation. - 5. _Activate further modules_: → Go to [Project settings → Modules]({{opSetting:base_url}}/projects/demo-construction-project/settings/modules). - 6. _Check out the tile view to get an overview of your BCF issues:_ → Go to [Work packages]({{opSetting:base_url}}/projects/demo-construction-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22id%3Aasc%22%2C%22f%22%3A%5B%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22card%22%7D) - 7. _Agile working? Check out our brand new boards:_ → Go to [Boards]({{opSetting:base_url}}/projects/demo-construction-project/boards) + 1. Projenize yeni üyeler davet edin_: → Proje navigasyonunda [Üyeler]({{opSetting:base_url}}/projects/demo-construction-project/members) bölümüne gidin. + 2. Projelerinizdeki çalışmaları görüntüleyin_: → [İş paketleri]({{opSetting:base_url}}/projects/demo-construction-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22bcfIssueAssociated%22%2C%22o%22%3A%22%3D%22%2C%22v%22%3A%5B%22f%22%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D) proje navigasyonunda. + 3. Yeni bir iş paketi oluşturun_: → [İş paketleri → Oluştur]({{opSetting:base_url}}/projects/demo-construction-project/work_packages/new?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22bcfIssueAssociated%22%2C%22o%22%3A%22%3D%22%2C%22v%22%3A%5B%22f%22%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D&type=11). + 4. Bir Gantt şeması oluşturun ve güncelleyin_: → [Gantt şeması]'na gidin({{opSetting:base_url}}/projects/demo-construction-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22assignee%22%2C%22responsible%22%5D%2C%22tv%22%3Atrue%2C%22tzl%22%3A%22weeks%22%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D) proje navigasyonuna gidin. + 5. Diğer modülleri etkinleştirin_: → [Proje ayarları → Modüller]({{opSetting:base_url}}/projects/demo-construction-project/settings/modules) bölümüne gidin. + 6. BCF sorunlarınıza genel bir bakış için kutucuk görünümüne göz atın:_ → [İş paketleri]({{opSetting:base_url}}/projects/demo-construction-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22id%3Aasc%22%2C%22f%22%3A%5B%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22card%22%7D) + 7. _Agile mi çalışıyorsunuz? Yepyeni panolarımıza göz atın:_ → [Panolar]({{opSetting:base_url}}/projects/demo-construction-project/boards) - Here you will find our [User Guides](https://www.openproject.org/docs/user-guide/). - Please let us know if you have any questions or need support. Contact us: [support\[at\]openproject.com](mailto:support@openproject.com). + adresine gidin Burada [Kullanıcı Kılavuzlarımızı](https://www.openproject.org/docs/user-guide/) bulacaksınız. + Herhangi bir sorunuz varsa veya desteğe ihtiyacınız varsa lütfen bize bildirin. Bize ulaşın: [support\[at\]openproject.com](mailto:support@openproject.com). item_4: options: name: Üyeler @@ -149,7 +149,7 @@ tr: options: name: Aşamalar demo-planning-constructing-project: - name: "(Demo) Planning & constructing" + name: "(Demo) Planlama ve yapılandırma" status_explanation: Tüm görevler programa uygundur. İlgili kişiler görevlerini biliyor. Sistem tamamen kurulmuştur. description: Bu, bu demo planlama ve inşa projesinin hedeflerinin kısa bir özetidir. news: @@ -179,21 +179,21 @@ tr: options: name: Başlarken text: | - We are glad you joined! We suggest to try a few things to get started in OpenProject. + Katıldığınız için çok mutluyuz! OpenProject'e başlamak için birkaç şey denemenizi öneririz. - Here you will find the classical roles, some workflows and work packages for your construction project. + Burada inşaat projeniz için klasik rolleri, bazı iş akışlarını ve iş paketlerini bulacaksınız. - _Try the following steps:_ + Aşağıdaki adımları deneyin:_ - 1. _Invite new members to your project:_ → Go to [Members]({{opSetting:base_url}}/projects/demo-planning-constructing-project/members) in the project navigation. - 2. _View the work in your projects:_ → Go to [Work packages]({{opSetting:base_url}}/projects/demo-planning-constructing-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22bcfIssueAssociated%22%2C%22o%22%3A%22%3D%22%2C%22v%22%3A%5B%22f%22%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D) in the project navigation. - 3. _Create a new work package:_ → Go to [Work packages → Create]({{opSetting:base_url}}/projects/demo-planning-constructing-project/work_packages/new?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22bcfIssueAssociated%22%2C%22o%22%3A%22%3D%22%2C%22v%22%3A%5B%22f%22%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D&type=11). - 4. _Create and update a Gantt chart:_ → Go to [Gantt chart]({{opSetting:base_url}}/projects/demo-planning-constructing-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22assignee%22%2C%22responsible%22%5D%2C%22tv%22%3Atrue%2C%22tzl%22%3A%22weeks%22%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D) in the project navigation. - 5. _Activate further modules:_ → Go to [Project settings → Modules]({{opSetting:base_url}}/projects/demo-planning-constructing-project/settings/modules). - 6. _Working agile? Create a new board:_ → Go to [Boards]({{opSetting:base_url}}/projects/demo-planning-constructing-project/boards) + 1. Projenize yeni üyeler davet edin:_ → Proje navigasyonunda [Üyeler]({{opSetting:base_url}}/projects/demo-planning-constructing-project/members) bölümüne gidin. + 2. Projelerinizdeki çalışmaları görüntüleyin:_ → [İş paketleri]({{opSetting:base_url}}/projects/demo-planning-constructing-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22bcfIssueAssociated%22%2C%22o%22%3A%22%3D%22%2C%22v%22%3A%5B%22f%22%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D) proje navigasyonunda. + 3. Yeni bir iş paketi oluşturun:_ → [İş paketleri → Oluştur]({{opSetting:base_url}}/projects/demo-planning-constructing-project/work_packages/new?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22bcfIssueAssociated%22%2C%22o%22%3A%22%3D%22%2C%22v%22%3A%5B%22f%22%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D&type=11). + 4. Bir Gantt şeması oluşturun ve güncelleyin:_ → [Gantt şeması]'na gidin({{opSetting:base_url}}/projects/demo-planning-constructing-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22assignee%22%2C%22responsible%22%5D%2C%22tv%22%3Atrue%2C%22tzl%22%3A%22weeks%22%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D) proje navigasyonunda. + 5. Diğer modülleri etkinleştirin:_ → [Proje ayarları → Modüller]({{opSetting:base_url}}/projects/demo-planning-constructing-project/settings/modules) bölümüne gidin. + 6. Çevik mi çalışıyorsunuz? Yeni bir pano oluşturun:_ → [Panolar]({{opSetting:base_url}}/projects/demo-planning-constructing-project/boards) - Here you will find our [User Guides](https://www.openproject.org/docs/user-guide/). - Please let us know if you have any questions or need support. Contact us: [support\[at\]openproject.com](mailto:support@openproject.com). + Burada [Kullanıcı Kılavuzlarımızı](https://www.openproject.org/docs/user-guide/) bulacaksınız. + Herhangi bir sorunuz varsa veya desteğe ihtiyacınız varsa lütfen bize bildirin. Bize ulaşın: [support\[at\]openproject.com](mailto:support@openproject.com). item_4: options: name: Üyeler @@ -205,17 +205,17 @@ tr: name: Aşamalar work_packages: item_0: - subject: Project kick off construction project + subject: Proje başlangıç inşaat projesi description: |- - The project kick off initializes the start of the project in your company. Everybody being part of this project should be invited to the kick off for a first briefing. + Proje başlangıcı, şirketinizde projenin başlangıcını başlatır. Bu projenin bir parçası olan herkes ilk brifing için başlangıç toplantısına davet edilmelidir. - The next step could be checking out the timetable and adjusting the appointments, by looking at the [Gantt chart]({{opSetting:base_url}}/projects/demo-construction-project/work_packages?query_props=%7B%22c%22%3A%5B%22id%22%2C%22subject%22%2C%22startDate%22%2C%22dueDate%22%5D%2C%22tv%22%3Atrue%2C%22tzl%22%3A%22weeks%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22status%22%2C%22o%22%3A%22o%22%2C%22v%22%3A%5B%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%7D). + Bir sonraki adım, [Gantt şemasına] bakarak zaman çizelgesini kontrol etmek ve randevuları ayarlamak olabilir ({{opSetting:base_url}}/projects/demo-construction-project/work_packages?query_props=%7B%22c%22%3A%5B%22id%22%2C%22subject%22%2C%22startDate%22%2C%22dueDate%22%5D%2C%22tv%22%3Atrue%2C%22tzl%22%3A%22weeks%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22status%22%2C%22o%22%3A%22o%22%2C%22v%22%3A%5B%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%7D). item_1: subject: Temel Değerleme - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + description: Bu tür hiyerarşik olarak "Clash" ve "Request"türlerinin bir üstüdür, dolayısıyla genel bir notu temsil eder. children: item_0: - subject: Gathering first project information + subject: İlk proje bilgilerinin toplanması description: |- ## Hedef @@ -228,63 +228,63 @@ tr: * Her ihtiyaç, ilgili iş paketleriyle birlikte bir görevi temsil etmelidir * Maliyet tahmini ve zaman çerçevesi türetilmelidir item_1: - subject: Summarize the results + subject: Sonuçları özetleyin description: |- - ## Goal + ## Hedef - * Create a useful overview of the results - * Check what has been done and summarize the results - * Communicate all the relevant results with the customer - * Identify the fundamental boundary conditions of the project + * Sonuçlara faydalı bir genel bakış oluşturmak + * Ne yapıldığını kontrol etmek ve sonuçları özetlemek + * İlgili tüm sonuçları müşteriye iletmek + * Projenin temel sınır koşullarını belirlemek - ## Description + ## Açıklama - * Each topic gets its own overview which will be used as a catalogue of results - * This overview informs all participants about the decisions made - * ... + * Her konu, sonuçların bir kataloğu olarak kullanılacak kendi genel bakışına sahip olur + * Bu genel bakış, tüm katılımcıları alınan kararlar hakkında bilgilendirir + * ... item_2: - subject: End of basic evaluation - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + subject: Temel değerlendirmenin sonu + description: Bu tür hiyerarşik olarak "Clash" ve "Request"türlerinin bir üstüdür, dolayısıyla genel bir notu temsil eder. item_2: subject: Ön planlama - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + description: Bu tür hiyerarşik olarak "Clash" ve "Request"türlerinin bir üstüdür, dolayısıyla genel bir notu temsil eder. children: item_0: subject: İlk taslağın geliştirilmesi description: |- - ## Goal + ## Hedef - * Create a useful overview of the results - * Check what has been done and summarize the results - * Communicate all the relevant results with the customer - * Identify the fundamental boundary conditions of the project + * Sonuçlara faydalı bir genel bakış oluşturmak + * Ne yapıldığını kontrol etmek ve sonuçları özetlemek + * İlgili tüm sonuçları müşteriye iletmek + * Projenin temel sınır koşullarını belirlemek - ## Description + ## Açıklama - * Each topic gets its own overview which will be used as a catalogue of results - * This overview informs all participants about the decisions made - * ... + * Her konu, sonuçların bir kataloğu olarak kullanılacak kendi genel bakışına sahip olur + * Bu genel bakış, tüm katılımcıları alınan kararlar hakkında bilgilendirir + * ... item_1: subject: Sonuçların özetlenmesi description: |- - ## Goal + ## Hedef - * Create a useful overview of the results - * Check what has been done and summarize the results - * Communicate all the relevant results with the customer - * Identify the fundamental boundary conditions of the project + * Sonuçlara faydalı bir genel bakış oluşturmak + * Ne yapıldığını kontrol etmek ve sonuçları özetlemek + * İlgili tüm sonuçları müşteriye iletmek + * Projenin temel sınır koşullarını belirlemek - ## Description + ## Açıklama - * Each topic gets its own overview which will be used as a catalogue of results - * This overview informs all participants about the decisions made - * ... + * Her konu, sonuçların bir kataloğu olarak kullanılacak kendi genel bakışına sahip olur + * Bu genel bakış, tüm katılımcıları alınan kararlar hakkında bilgilendirir + * ... item_3: - subject: Passing of preliminary planning - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + subject: Ön planlamanın geçmesi + description: Bu tür hiyerarşik olarak "Clash" ve "Request"türlerinin bir üstüdür, dolayısıyla genel bir notu temsil eder. item_4: subject: Tasarım planlaması - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + description: Bu tür hiyerarşik olarak "Clash" ve "Request"türlerinin bir üstüdür, dolayısıyla genel bir notu temsil eder. children: item_0: subject: Bitirme tasarımı @@ -302,7 +302,7 @@ tr: * ... item_1: subject: Tasarım dondurma - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + description: Bu tür hiyerarşik olarak "Clash" ve "Request"türlerinin bir üstüdür, dolayısıyla genel bir notu temsil eder. item_5: subject: İnşaat aşaması children: @@ -321,49 +321,49 @@ tr: * Ekibin bir araya getirilmesi * ... item_1: - subject: Foundation + subject: Yapı Temeli description: |- - ## Goal + ## Hedef - * Laying of the foundation stone - * ... + * Temel taşının döşenmesi + * ... - ## Description + ## Açıklama - * Setting up the concrete mixer - * Setting up the supply chain for the concrete - * ... + * Beton mikserinin kurulması + * Beton için tedarik zincirinin kurulması + * ... item_2: - subject: Building construction + subject: Bina İnşası description: |- - ## Goal + ## Hedef - * Topping out ceremony - * Walls and ceilings are done - * ... + * Tamamlama töreni + * Duvarlar ve tavanlar tamamlandı + * ... - ## Description + ## Açıklama - * Creating all structural levels of the building - * Installing doors and windows - * Finishing the roof structure - * ... + * Binanın tüm yapısal seviyelerinin oluşturulması + * Kapı ve pencerelerin takılması + * Çatı yapısının bitirilmesi + * ... item_3: - subject: Finishing the facade + subject: Dış cephenin tamamlanması description: |- - ## Goal + ## Hedef - * Facade is done - * Whole building is waterproof - * ... + * Cephe tamamlandı + * Tüm bina su geçirmez + * ... - ## Description + ## Açıklama - * Install all elements for the facade - * Finish the roof - * ... + * Cephe için tüm elemanları kurun + * Çatıyı bitirin + * ... item_4: - subject: Installing the building service systems + subject: Bina servis sistemlerinin kurulması description: |- ## Hedef @@ -433,24 +433,24 @@ tr: options: name: Başlarken text: | - We are glad you joined! We suggest to try a few things to get started in OpenProject. + Katıldığınız için çok mutluyuz! OpenProject'e başlamak için birkaç şey denemenizi öneririz. - This demo project offers roles, workflows and work packages that are specialized for BIM. + Bu demo proje, BIM için özelleşmiş roller, iş akışları ve iş paketleri sunmaktadır. - _Try the following steps:_ + Aşağıdaki adımları deneyin:_ - 1. _Invite new members to your project:_ → Go to [Members]({{opSetting:base_url}}/projects/demo-bim-project/members) in the project navigation. - 2. _Upload and view 3D-models in IFC format:_ → Go to [BCF]({{opSetting:base_url}}/projects/demo-bim-project/bcf) in the project navigation. - 3. _Create and manage BCF issues linked directly in the IFC model:_ → Go to [BCF]({{opSetting:base_url}}/projects/demo-bim-project/bcf) → Create. - 4. _View the work in your projects:_ → Go to [Work packages]({{opSetting:base_url}}/projects/demo-bim-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22bcfIssueAssociated%22%2C%22o%22%3A%22%3D%22%2C%22v%22%3A%5B%22f%22%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D) in the project navigation. - 5. _Create a new work package:_ → Go to [Work packages → Create]({{opSetting:base_url}}/projects/demo-bim-project/work_packages/new?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22bcfIssueAssociated%22%2C%22o%22%3A%22%3D%22%2C%22v%22%3A%5B%22f%22%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D&type=11). - 6. _Create and update a Gantt chart:_ → Go to [Gantt chart]({{opSetting:base_url}}/projects/demo-bim-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22assignee%22%2C%22responsible%22%5D%2C%22tv%22%3Atrue%2C%22tzl%22%3A%22weeks%22%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D) in the project navigation. - 7. _Activate further modules:_ → Go to [Project settings → Modules]({{opSetting:base_url}}/projects/demo-bim-project/settings/modules). - 8. _Check out the tile view to get an overview of your BCF issues:_ → Go to [Work packages]({{opSetting:base_url}}/projects/demo-bim-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22id%3Aasc%22%2C%22f%22%3A%5B%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22card%22%7D) - 9. _Working agile? Create a new board:_ → Go to [Boards]({{opSetting:base_url}}/projects/demo-bim-project/boards) + 1. Projenize yeni üyeler davet edin:_ → Proje navigasyonunda [Üyeler]({{opSetting:base_url}}/projects/demo-bim-project/members) bölümüne gidin. + 2. 3D modelleri IFC formatında yükleyin ve görüntüleyin:_ → Proje navigasyonunda [BCF]({{opSetting:base_url}}/projects/demo-bim-project/bcf) bölümüne gidin. + 3. Doğrudan IFC modeline bağlı BCF sorunlarını oluşturun ve yönetin:_ → [BCF]({{opSetting:base_url}}/projects/demo-bim-project/bcf) → Oluştur'a gidin. + 4. Projelerinizdeki çalışmaları görüntüleyin:_ → [İş paketleri]({{opSetting:base_url}}/projects/demo-bim-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22bcfIssueAssociated%22%2C%22o%22%3A%22%3D%22%2C%22v%22%3A%5B%22f%22%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D) proje navigasyonunda. + 5. Yeni bir iş paketi oluşturun:_ → [İş paketleri → Oluştur]({{opSetting:base_url}}/projects/demo-bim-project/work_packages/new?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22bcfIssueAssociated%22%2C%22o%22%3A%22%3D%22%2C%22v%22%3A%5B%22f%22%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D&type=11). + 6. Bir Gantt şeması oluşturun ve güncelleyin:_ → [Gantt şeması]'na gidin({{opSetting:base_url}}/projects/demo-bim-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22assignee%22%2C%22responsible%22%5D%2C%22tv%22%3Atrue%2C%22tzl%22%3A%22weeks%22%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22list%22%7D) proje navigasyonunda. + 7. Diğer modülleri etkinleştirin:_ → [Proje ayarları → Modüller]({{opSetting:base_url}}/projects/demo-bim-project/settings/modules) bölümüne gidin. + 8. BCF sorunlarınıza genel bir bakış için kutucuk görünümüne göz atın:_ → [İş paketleri]({{opSetting:base_url}}/projects/demo-bim-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22priority%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22id%3Aasc%22%2C%22f%22%3A%5B%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22card%22%7D) + 9. Çevik mi çalışıyorsunuz? Yeni bir pano oluşturun:_ → [Panolar]({{opSetting:base_url}}/projects/demo-bim-project/boards) - Here you will find our [User Guides](https://www.openproject.org/docs/user-guide/). - Please let us know if you have any questions or need support. Contact us: [support\[at\]openproject.com](mailto:support@openproject.com). + adresine gidin Burada [Kullanıcı Kılavuzlarımızı](https://www.openproject.org/docs/user-guide/) bulacaksınız. + Herhangi bir sorunuz varsa veya desteğe ihtiyacınız varsa lütfen bize bildirin. Bize ulaşın: [support\[at\]openproject.com](mailto:support@openproject.com). item_4: options: name: Üyeler @@ -462,17 +462,17 @@ tr: name: Aşamalar work_packages: item_0: - subject: Project kick off creating BIM model + subject: BIM modeli oluşturarak proje başlangıcı description: |- - The project Kickoff initializes the start of the project in your company. The whole project team should be invited to the Kickoff for a first briefing. + Proje başlangıcı, şirketinizde projenin başlangıcını başlatır. Bu projenin bir parçası olan herkes ilk brifing için başlangıç toplantısına davet edilmelidir. - The next step could be to check out the timetable and adjusting the appointments, by looking at the [Gantt chart]({{opSetting:base_url}}/projects/demo-bim-project/work_packages?query_props=%7B%22c%22%3A%5B%22id%22%2C%22subject%22%2C%22startDate%22%2C%22dueDate%22%5D%2C%22tv%22%3Atrue%2C%22tzl%22%3A%22weeks%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22status%22%2C%22o%22%3A%22o%22%2C%22v%22%3A%5B%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%7D). + Bir sonraki adım, [Gantt şemasına] bakarak zaman çizelgesini kontrol etmek ve randevuları ayarlamak olabilir ({{opSetting:base_url}}/projects/demo-construction-project/work_packages?query_props=%7B%22c%22%3A%5B%22id%22%2C%22subject%22%2C%22startDate%22%2C%22dueDate%22%5D%2C%22tv%22%3Atrue%2C%22tzl%22%3A%22weeks%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22status%22%2C%22o%22%3A%22o%22%2C%22v%22%3A%5B%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%7D). item_1: subject: Proje hazırlığı - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + description: Bu tür hiyerarşik olarak "Clash" ve "Request"türlerinin bir üstüdür, dolayısıyla genel bir notu temsil eder. children: item_0: - subject: Gathering the project specific data and information for the BIM model + subject: BIM modeli için projeye özel veri ve bilgilerin toplanması description: |- ## Hedef @@ -488,139 +488,139 @@ tr: * Teslim aşaması için bir strateji * ... item_1: - subject: Creating the BIM execution plan + subject: BIM yürütme planının oluşturulması description: |- - # Goal + # Hedef - * A BIM execution plan will be defined according to the exchange requirements specifications (ERS) - * All team members and partners have a plan on how to reach each of the project goals + * Değişim gereksinimleri spesifikasyonlarına (ERS) göre bir BIM yürütme planı tanımlanacaktır + * Tüm ekip üyeleri ve ortakları, proje hedeflerinin her birine nasıl ulaşılacağına dair bir plana sahiptir - # Description + # Açıklama - * Depending on the identifies use cases, the individual Information Delivery Manuals will be defined - * To handle the technological interfaces, a software topology will be defined and analyzed and verified - * ... + * Tanımlanan kullanım durumlarına bağlı olarak, bireysel Bilgi Teslim Kılavuzları tanımlanacaktır + * Teknolojik arayüzleri ele almak için bir yazılım topolojisi tanımlanacak ve analiz edilip doğrulanacaktır + * ... item_2: - subject: Completion of the BIM execution plan - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + subject: BIM yürütme planının oluşturulması + description: Bu tür hiyerarşik olarak "Clash" ve "Request"türlerinin bir üstüdür, dolayısıyla genel bir notu temsil eder. item_2: - subject: End of preparation phase - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + subject: Hazırlık aşamasının sonu + description: Bu tür hiyerarşik olarak "Clash" ve "Request"türlerinin bir üstüdür, dolayısıyla genel bir notu temsil eder. item_3: - subject: Creating initial BIM model - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + subject: İlk BIM modelinin oluşturulması + description: Bu tür hiyerarşik olarak "Clash" ve "Request"türlerinin bir üstüdür, dolayısıyla genel bir notu temsil eder. children: item_0: - subject: Modelling initial BIM model + subject: İlk BIM modelinin oluşturulması description: |- - # Goal + # Hedef - * Modelling the initial BIM model - * Creating a BIM model for the whole project team + * İlk BIM modelinin modellenmesi + * Tüm proje ekibi için bir BIM modeli oluşturulması - # Description + # Açıklama - * According to the gathered data from the customer, the initial model will be modelled - * The model shall be modelled according to the LOD Matrices and contain the information needed - * ... + * Müşteriden toplanan verilere göre ilk model modellenecektir + * Model, LOD Matrislerine göre modellenecek ve ihtiyaç duyulan bilgileri içerecektir + * ... item_1: - subject: Initial, internal model check and revising + subject: Başlangıç, dahili model kontrolü ve revizyonu description: |- - # Goal + # Hedef - * Submitting a BIM model according to the defined standards + * Tanımlanan standartlara göre bir BIM modelinin sunulması - # Description + # Açıklama - * The model shall be checked, according to the defined standards (conventions, LOD, ...) and revised - * ... + * Model, tanımlanan standartlara (konvansiyonlar, LOD, ...) göre kontrol edilecek ve revize edilecektir + * ... item_2: - subject: Submitting initial BIM model - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + subject: İlk BIM modelinin oluşturulması + description: Bu tür hiyerarşik olarak "Clash" ve "Request"türlerinin bir üstüdür, dolayısıyla genel bir notu temsil eder. item_4: - subject: Modelling, first cycle - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + subject: Modelleme, ilk döngü + description: Bu tür hiyerarşik olarak "Clash" ve "Request"türlerinin bir üstüdür, dolayısıyla genel bir notu temsil eder. children: item_0: - subject: Referencing external BIM models + subject: Harici BIM modellerine referans verme description: |- - # Goal + # Hedef - * Having a foundation for developing the internal model/ offering answers - * Using the external model to develop the internal model + * Dahili modeli geliştirmek için bir temele sahip olmak / cevaplar sunmak + * Dahili modeli geliştirmek için harici modeli kullanmak - # Description + # Açıklama - * The external model will be referenced in the BIM platform, thus used for modelling the internal model - * ... + * Harici model BIM platformunda referans alınacak, böylece dahili modelin modellenmesi için kullanılacaktır + * ... item_1: - subject: Modelling the BIM model + subject: İlk BIM modelinin oluşturulması description: |- - # Goal + # Hedef - * Creating a BIM model for the project - * Creating a BIM model for the whole project team + * Proje için bir BIM modeli oluşturmak + * Tüm proje ekibi için bir BIM modeli oluşturmak - # Description + # Açıklama - * The model will be created according to the BIM execution plan - * ... + * Model, BIM yürütme planına göre oluşturulacaktır + * ... item_2: - subject: First Cycle, internal model check and revising + subject: Başlangıç, dahili model kontrolü ve revizyonu description: |- - # Goal + # Hedef - * Submitting a BIM model according to the defined standards + * Tanımlanan standartlara göre bir BIM modelinin sunulması - # Description + # Açıklama - * The model shall be checked, according to the defined standards (conventions, LOD, ...) and revised. - * ... + * Model, tanımlanan standartlara (konvansiyonlar, LOD, ...) göre kontrol edilecek ve revize edilecektir + * ... item_3: - subject: Submitting BIM model - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + subject: BIM modelinin gönderilmesi + description: Bu tür hiyerarşik olarak "Clash" ve "Request"türlerinin bir üstüdür, dolayısıyla genel bir notu temsil eder. item_5: - subject: Coordination, first cycle - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + subject: Koordinasyon, ilk döngü + description: Bu tür hiyerarşik olarak "Clash" ve "Request"türlerinin bir üstüdür, dolayısıyla genel bir notu temsil eder. children: item_0: - subject: Coordinate the different BIM models + subject: Farklı BIM modellerini koordine edin description: |- - # Goal + # Hedef - * Assemble the different BIM models of the whole project team - * Coordinate the identified issues + * Tüm proje ekibinin farklı BIM modellerini bir araya getirin + * Belirlenen sorunları koordine edin - # Description + # Açıklama - * The different BIM models will be assembled and checked - * The identified model specific issues will be communicated via BCF files - * ... + * Farklı BIM modelleri bir araya getirilecek ve kontrol edilecektir + * Belirlenen modele özgü sorunlar BCF dosyaları aracılığıyla iletilecektir + * ... item_1: subject: Sorun yönetimi, ilk döngü item_2: - subject: Finishing coordination, first cycle - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + subject: Koordinasyonun sonlanması, ilk döngü + description: Bu tür hiyerarşik olarak "Clash" ve "Request"türlerinin bir üstüdür, dolayısıyla genel bir notu temsil eder. item_6: - subject: Modelling & coordinating, second cycle - description: "## Goal\r\n\r\n* ...\r\n\r\n## Description\r\n\r\n* \\ ..." + subject: Modelleme ve koordinasyon, ikinci döngü + description: "## Hedef\r\n\r\n* ...\r\n\r\n## Açıklama\r\n\r\n* \\ ..." item_7: - subject: Modelling & coordinating, ... cycle - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + subject: Modelleme ve koordinasyon, ... döngü + description: Bu tür hiyerarşik olarak "Clash" ve "Request"türlerinin bir üstüdür, dolayısıyla genel bir notu temsil eder. item_8: - subject: Modelling & coordinating, (n-th minus 1) cycle - description: "## Goal\r\n\r\n* ...\r\n\r\n## Description\r\n\r\n* \\ ..." + subject: Modelleme ve koordinasyon, (n'inci eksi 1) döngü + description: "## Hedef\r\n\r\n* ...\r\n\r\n## Açıklama\r\n\r\n* \\ ..." item_9: - subject: Modelling & coordinating n-th cycle - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + subject: Modelleme ve koordinasyon (n'inci) döngü + description: Bu tür hiyerarşik olarak "Clash" ve "Request"türlerinin bir üstüdür, dolayısıyla genel bir notu temsil eder. item_10: - subject: Finishing modelling & coordinating, n-th cycle - description: This type is hierarchically a parent of the types "Clash" and "Request", thus represents a general note. + subject: Modelleme ve koordinasyon tamamlanıyor (n'inci) döngü + description: Bu tür hiyerarşik olarak "Clash" ve "Request"türlerinin bir üstüdür, dolayısıyla genel bir notu temsil eder. item_11: - subject: Use model for construction phase + subject: İnşaat aşaması için model kullanın children: item_0: - subject: Handover model for construction crew + subject: İnşaat ekibi için devir teslim modeli description: |- ## Hedef @@ -634,36 +634,36 @@ tr: * Tüm nesneler, atanan görevler için gereken bilgilere sahip olmalıdır. Eğer yoksa, modelin veri zenginleştirmesinin yapılması gerekir * ... item_1: - subject: Construct the building + subject: Binanın inşası description: |- - ## Goal + ## Hedef - * New issues realized on construction site will be handled model based - * Issues will be documented by using the BCF files and the BIM model + * Şantiyede gerçekleşen yeni sorunlar model tabanlı olarak ele alınacaktır + * Sorunlar BCF dosyaları ve BIM modeli kullanılarak belgelenecektir - ## Description + ## Açıklama - * New issues will be documented using BCF files as sticky notes for the model - * The BCF files will be used to assign, track and correct issues - * ... + * Yeni sorunlar BCF dosyaları model için yapışkan notlar olarak kullanılarak belgelenecektir + * BCF dosyaları sorunları atamak, izlemek ve düzeltmek için kullanılacaktır + * ... item_2: - subject: Finish construction + subject: Binanın tamamlanması item_12: - subject: Issue management, construction phase + subject: Sorun yönetimi, inşaat aşaması item_13: - subject: Handover for Facility Management + subject: Tesis Yönetimi için Devir Teslim description: |- - ## Goal + ## Hedef - * The BIM model will be used for the Facility Management - * The model provides all the relevant information for commissioning and operating the building - * ... + * BIM modeli Tesis Yönetimi için kullanılacaktır + * Model, binanın devreye alınması ve işletilmesi için ilgili tüm bilgileri sağlar + * ... - ## Description + ## Açıklama - * The model contains the relevant information for the facility manager - * The model can be used for the operating system of the building - * ... + * Model, tesis yöneticisi için ilgili bilgileri içerir + * Model, binanın işletim sistemi için kullanılabilir + * ... item_14: subject: Varlık Yönetimi description: Binanızın keyfini çıkarın :) @@ -709,23 +709,23 @@ tr: options: name: Başlarken text: | - We are glad you joined! We suggest to try a few things to get started in OpenProject. + Katıldığınız için çok mutluyuz! OpenProject'e başlamak için birkaç şey denemenizi öneririz. - This demo project shows BCF management functionalities. + Bu demo proje BCF yönetim işlevlerini göstermektedir. - _Try the following steps:_ + Aşağıdaki adımları deneyin:_ - 1. _Invite new members to your project:_ → Go to [Members]({{opSetting:base_url}}/projects/demo-bcf-management-project/members?show_add_members=true) in the project navigation. - 2. _Upload and view 3D-models in IFC format:_ → Go to [BCF]({{opSetting:base_url}}/projects/demo-bim-project/bcf) in the project navigation. - 3. _Create and manage BCF issues linked directly in the IFC model:_ → Go to [BCF]({{opSetting:base_url}}/projects/demo-bim-project/bcf) → Create. - 4. _View the BCF files in your project:_ → Go to [BCF]({{opSetting:base_url}}/projects/demo-bcf-management-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22status%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22id%3Aasc%22%2C%22f%22%3A%5B%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22card%22%7D) in the project navigation. - 5. _Load your BCF files:_ → Go to [BCF → Import.]({{opSetting:base_url}}/projects/demo-bcf-management-project/issues/upload) - 6. _Create and update a Gantt chart:_ → Go to [Gantt chart]({{opSetting:base_url}}/projects/demo-bcf-management-project/work_packages?query_props=%7B%22c%22%3A%5B%22id%22%2C%22subject%22%2C%22startDate%22%2C%22dueDate%22%5D%2C%22tv%22%3Atrue%2C%22tzl%22%3A%22days%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22status%22%2C%22o%22%3A%22o%22%2C%22v%22%3A%5B%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%7D) in the project navigation. - 7. _Activate further modules:_ → Go to [Project settings → Modules.]({{opSetting:base_url}}/projects/demo-bcf-management-project/settings/modules) - 8. _You love the agile approach? Create a board:_ → Go to [Boards]({{opSetting:base_url}}/projects/demo-bcf-management-project/boards). + 1. Projenize yeni üyeler davet edin:_ → Proje navigasyonunda [Üyeler]({{opSetting:base_url}}/projects/demo-bcf-management-project/members?show_add_members=true) bölümüne gidin. + 2. 3D modelleri IFC formatında yükleyin ve görüntüleyin:_ → Proje navigasyonunda [BCF]({{opSetting:base_url}}/projects/demo-bim-project/bcf) seçeneğine gidin. + 3. Doğrudan IFC modeline bağlı BCF sorunlarını oluşturun ve yönetin:_ → [BCF]({{opSetting:base_url}}/projects/demo-bim-project/bcf) → Oluştur'a gidin. + 4. Projenizdeki BCF dosyalarını görüntüleyin:_ → [BCF]({{opSetting:base_url}}/projects/demo-bcf-management-project/work_packages?query_props=%7B%22c%22%3A%5B%22type%22%2C%22id%22%2C%22subject%22%2C%22status%22%2C%22assignee%22%2C%22priority%22%5D%2C%22hl%22%3A%22status%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22id%3Aasc%22%2C%22f%22%3A%5B%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%2C%22dr%22%3A%22card%22%7D) proje navigasyonuna gidin. + 5. BCF dosyalarınızı yükleyin:_ → [BCF → İçe Aktar]({{opSetting:base_url}}/projects/demo-bcf-management-project/issues/upload) + adresine gidin. Bir Gantt şeması oluşturun ve güncelleyin:_ → [Gantt şeması] bölümüne gidin({{opSetting:base_url}}/projects/demo-bcf-management-project/work_packages?query_props=%7B%22c%22%3A%5B%22id%22%2C%22subject%22%2C%22startDate%22%2C%22dueDate%22%5D%2C%22tv%22%3Atrue%2C%22tzl%22%3A%22days%22%2C%22hi%22%3Atrue%2C%22g%22%3A%22%22%2C%22t%22%3A%22startDate%3Aasc%22%2C%22f%22%3A%5B%7B%22n%22%3A%22status%22%2C%22o%22%3A%22o%22%2C%22v%22%3A%5B%5D%7D%5D%2C%22pa%22%3A1%2C%22pp%22%3A100%7D) proje navigasyonuna gidin. + 7. Diğer modülleri etkinleştirin:_ → [Proje ayarları → Modüller.]({{opSetting:base_url}}/projects/demo-bcf-management-project/settings/modules) + 8. Çevik yaklaşımı seviyor musunuz? Bir pano oluşturun:_ → [Panolar]({{opSetting:base_url}}/projects/demo-bcf-management-project/boards) adresine gidin. - Here you will find our [User Guides](https://www.openproject.org/docs/user-guide/). - Please let us know if you have any questions or need support. Contact us: [support\[at\]openproject.com](mailto:support@openproject.com). + Burada [Kullanıcı Kılavuzlarımızı](https://www.openproject.org/docs/user-guide/) bulacaksınız. + Herhangi bir sorunuz varsa veya desteğe ihtiyacınız varsa lütfen bize bildirin. Bize ulaşın: [support\[at\]openproject.com](mailto:support@openproject.com). item_4: options: name: Üyeler diff --git a/modules/costs/config/locales/crowdin/tr.yml b/modules/costs/config/locales/crowdin/tr.yml index e9c15aa795d..a41b3afbe90 100644 --- a/modules/costs/config/locales/crowdin/tr.yml +++ b/modules/costs/config/locales/crowdin/tr.yml @@ -208,7 +208,7 @@ tr: setting_costs_currency_format: "Para biriminin biçimi" setting_enforce_tracking_start_and_end_times: "Başlangıç ve bitiş saatleri gerektir" setting_enforce_without_allow: "Başlangıç ve bitiş saatlerini zorunlu tutmak, bunlara izin vermeden mümkün değildir" - setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." + setting_allow_tracking_start_and_end_times_caption: "Zaman kaydı yaparken başlangıç ve bitiş zamanlarının girilmesini sağlar." setting_enforce_tracking_start_and_end_times_caption: "Zaman kaydı yaparken başlangıç ve bitiş zamanlarının girilmesini zorunlu hale getirir." text_assign_time_and_cost_entries_to_project: "Raporlanan saatleri ve maliyetleri projeye tahsis edin" text_destroy_cost_entries_question: "%{cost_entries} silmek üzere olduğunuz iş paketleri hakkında rapor edildi. Ne yapmak istiyorsun ?" diff --git a/modules/documents/config/locales/crowdin/tr.seeders.yml b/modules/documents/config/locales/crowdin/tr.seeders.yml index e9ca364a907..08b7e77bfda 100644 --- a/modules/documents/config/locales/crowdin/tr.seeders.yml +++ b/modules/documents/config/locales/crowdin/tr.seeders.yml @@ -7,14 +7,14 @@ tr: common: document_types: item_0: - name: Note + name: Notlar item_1: - name: Idea + name: Fikir item_2: - name: Proposal + name: Öneri item_3: - name: Specification + name: Özellik item_4: - name: Report + name: Rapor item_5: name: Belgeleme diff --git a/modules/documents/config/locales/crowdin/tr.yml b/modules/documents/config/locales/crowdin/tr.yml index 7f244d338d3..a3b0738aad0 100644 --- a/modules/documents/config/locales/crowdin/tr.yml +++ b/modules/documents/config/locales/crowdin/tr.yml @@ -27,13 +27,13 @@ tr: errors: models: document_type: - one_or_more_required: "Cannot delete the last document type" + one_or_more_required: "Son belge türü silinemiyor" models: document: "Belge" documents: "Belgeler" attributes: document: - content_binary: "Content binary" + content_binary: "Binary içerik" title: "Başlık" activity: filter: @@ -49,99 +49,99 @@ tr: edit_title: "Başlığı düzenle" subheader: filter: - label: "Document name filter" - placeholder: "Type here to search document names" + label: "Proje adı filtresi" + placeholder: "Belge adlarını aramak için buraya yazın" documents_list_blank_slate: - heading: "There are no documents yet" - description: "There are no documents in this view. You can click the button below to add one." + heading: "Henüz belge yok" + description: "Bu görünümde herhangi bir belge bulunmamaktadır. Eklemek için aşağıdaki butona tıklayabilirsiniz." document_categories_deprecation_notice: - heading: File categories are now called 'Document types' + heading: Dosya kategorileri artık 'Belge türleri' olarak adlandırılıyor description: |- - Your existing file categories have been converted to document types with the introduction of the new Documents module. - All existing documents have also been migrated to these new types. - primary_action: Configure document types - secondary_action: Learn more about the Documents module + Mevcut dosya kategorileriniz, yeni Belgeler modülünün kullanılmaya başlanmasıyla birlikte belge türlerine dönüştürülmüştür. + Mevcut tüm belgeler de bu yeni türlere taşınmıştır. + primary_action: Belge türlerini yapılandırma + secondary_action: Belgeler modülü hakkında daha fazla bilgi edinin document_type_actions: "Belge türü eylemleri" text_collaboration_disabled_notice: description: |- - Unable to open document because real-time text collaboration is disabled. - Please contact your administrator to enable real-time text collaboration if you want to access this document. + Gerçek zamanlı metin işbirliği devre dışı bırakıldığı için belge açılamıyor. + Bu belgeye erişmek istiyorsanız gerçek zamanlı metin işbirliğini etkinleştirmek için lütfen yöneticinize başvurun. show_edit_view: connection_error_notice: description: |- - Unable to open document because the real-time text collaboration server is unreachable. - Please contact the administrator if the problem persists. - action: Try again + Gerçek zamanlı metin işbirliği sunucusuna erişilemediği için belge açılamıyor. + Sorun devam ederse lütfen yöneticiyle iletişime geçin. + action: Tekrar deneyin connection_recovery_notice: - description: "The connection to the real-time text collaboration server has been restored." - tabs: "Document tabs" + description: "Gerçek zamanlı metin işbirliği sunucusuna bağlantı yeniden sağlandı." + tabs: "Belge sekmeleri" index_page: name: "İsim" type: "Tür" - updated_at: "Last edited" - label_legacy: "Legacy" + updated_at: "Son düzenlenen" + label_legacy: "Klasik" menu: - all: "All documents" + all: "Tüm belgeler" types: "Türler" - collaboration_settings: "Real-time collaboration" - last_updated_at: "Last saved %{time}." - active_editors: "Active editors" + collaboration_settings: "Gerçek zamanlı işbirliği" + last_updated_at: "Son kaydedilen %{time}." + active_editors: "Etkin düzenleyici" active_editors_count: - one: "1 active editor" + one: "1 aktif editör" other: "%{count} aktif editör" label_attachment_author: "Ek yazar" label_categories: "Kategoriler" new_category: "Yeni kategori" - new_type: "New type" + new_type: "Yeni tür" delete_dialog: - title: "Delete document" - heading: "Delete this document?" - confirmation_message_html: "This will permanently delete this document and all file attachments. Are you sure you want to do this?" + title: "Belgeyi sil" + heading: "Bu belgeyi silelim mi?" + confirmation_message_html: "Bu, bu belgeyi ve tüm dosya eklerini kalıcı olarak silecektir. Bunu yapmak istediğinizden emin misiniz?" delete_document_type_dialog: - title: Delete document type - heading: Delete this document type? + title: Belge türünü sil + heading: Bu belge türünü sileyim mi? confirmation_message: |- - The type "%{type_name}" is currently unused. Deleting this type will have no effect on existing documents. - select_reassign_to_label: Reassign documents to + "%{type_name}" türü şu anda kullanılmamaktadır. Bu türün silinmesinin mevcut belgeler üzerinde hiçbir etkisi olmayacaktır. + select_reassign_to_label: Belgeleri yeniden atama reassign_message: - one: The type "%{type_name}" is currently being used in %{document_count} document. Please select which type to reassign them to. - other: The type "%{type_name}" is currently being used in %{document_count} documents. Please select which type to reassign them to. + one: '"%{type_name}" türü şu anda %{document_count} belgesinde kullanılmaktadır. Lütfen bunları hangi türe yeniden atayacağınızı seçin.' + other: '"%{type_name}" türü şu anda %{document_count} belgelerinde kullanılmaktadır. Lütfen bunları hangi türe yeniden atayacağınızı seçin.' at_least_one_type_required: - title: Cannot delete document type - heading: Cannot delete the last document type - message: There must always be at least one document type configured. Create another one first if you want to delete this one. + title: Belge türü silinemiyor + heading: Son belge türü silinemiyor + message: Her zaman yapılandırılmış en az bir belge türü olmalıdır. Bunu silmek istiyorsanız önce başka bir tane oluşturun. admin: collaboration_settings: page_header: description: |- - When enabled, real-time collaboration allows multiple users to edit a document at the same time. - It requires a working %{hocuspocus_server_link} to function. + Etkinleştirildiğinde, gerçek zamanlı işbirliği birden fazla kullanıcının bir belgeyi aynı anda düzenlemesine olanak tanır. + Çalışması için çalışan bir %{hocuspocus_server_link} gerekir. banner: - none_writable: These values are configured via environment variables and cannot be edited here. - some_unwritable: Some values are configured via environment variables and cannot be edited here. + none_writable: Bu değerler ortam değişkenleri aracılığıyla yapılandırılır ve burada düzenlenemez. + some_unwritable: Bazı değerler ortam değişkenleri aracılığıyla yapılandırılır ve burada düzenlenemez. hocuspocus_server_url: - label: "Hocuspocus server URL" - caption: "The address of a working Hocuspocus server." + label: "Hocuspocus sunucu URL'si" + caption: "Çalışan bir Hocuspocus sunucusunun adresi." hocuspocus_server_secret: - label: "Client secret" - caption: "Paste the secret provided by the Hocuspocus server." - hocuspocus_server: Hocuspocus server + label: "İstemci anahtarı" + caption: "Hocuspocus sunucusu tarafından sağlanan sırrı yapıştırın." + hocuspocus_server: Hocuspocus sunucusu enable_text_collaboration: - heading: Real-time collaboration is not enabled + heading: Gerçek zamanlı işbirliği etkin değil description: |- - Once enabled, multiple users will be able to work together on a document at the same time. - All new documents will be based on a new editor (BlockNote) and will require a working connection to a - Hocuspocus server. - primary_action: Enable real-time collaboration - success: Real-time collaboration has been enabled. + Etkinleştirildikten sonra, birden fazla kullanıcı aynı anda bir belge üzerinde birlikte çalışabilecektir. + Tüm yeni belgeler yeni bir düzenleyiciye (BlockNote) dayanacak ve + Hocuspocus sunucusuna çalışan bir bağlantı gerektirecektir. + primary_action: Gerçek zamanlı işbirliğini etkinleştirin + success: Gerçek zamanlı işbirliği etkinleştirildi. disable_text_collaboration_dialog: - title: Disable real-time collaboration - heading: Disable real-time collaboration? + title: Gerçek zamanlı işbirliğini devre dışı bırakma + heading: Gerçek zamanlı işbirliğini devre dışı mı bırakıyorsunuz? confirmation_message: |- - All existing documents may become inaccessible. Please only do this if you are certain you want to disable - real-time collaboration and the BlockNote editor in this instance. - confirmation_checkbox_message: I understand that I might permanently lose data - success: Real-time collaboration has been disabled. + Mevcut tüm belgeler erişilemez hale gelebilir. Lütfen bunu yalnızca + gerçek zamanlı işbirliğini ve BlockNote düzenleyicisini bu durumda devre dışı bırakmak istediğinizden eminseniz yapın. + confirmation_checkbox_message: Verileri kalıcı olarak kaybedebileceğimi anlıyorum + success: Gerçek zamanlı işbirliği devre dışı bırakıldı. label_document_added: "Belge eklendi" label_document_new: "Yeni belge" label_document_plural: "Belgeler" diff --git a/modules/grids/config/locales/crowdin/js-tr.yml b/modules/grids/config/locales/crowdin/js-tr.yml index 7e94bf020cb..224eee3379e 100644 --- a/modules/grids/config/locales/crowdin/js-tr.yml +++ b/modules/grids/config/locales/crowdin/js-tr.yml @@ -16,7 +16,7 @@ tr: news: title: 'Haberler' project_description: - title: 'Description' + title: 'Açıklama' no_results: "Henüz bir açıklama yazılmadı. Biri 'Proje ayarları' içinde sağlanabilir." project_status: title: 'Durum' @@ -30,7 +30,7 @@ tr: project_status_beta: title: 'Durum (BETA)' subprojects: - title: 'Subitems' + title: 'Alt öğeler' project_favorites: title: 'Favori projeler' no_results: 'Şu anda favori projeniz yok. Favorilerinize bir proje eklemek için proje gösterge panelindeki yıldız simgesine tıklayın.' @@ -52,7 +52,7 @@ tr: title: 'İş paketleri tablosu' work_packages_graph: title: 'İş paketleri grafiği' - summary: "%{chartType} chart showing work packages which are %{description}." + summary: "%{chartType} %{description}olan iş paketlerini gösteren grafik." work_packages_calendar: title: 'Takvim' work_packages_overview: diff --git a/modules/grids/config/locales/crowdin/tr.yml b/modules/grids/config/locales/crowdin/tr.yml index b54e8c038b7..9f1197ec160 100644 --- a/modules/grids/config/locales/crowdin/tr.yml +++ b/modules/grids/config/locales/crowdin/tr.yml @@ -2,20 +2,20 @@ tr: grids: label_widget_in_grid: "%{grid_name} ızgarasında bulunan bileşen" widgets: - empty: "This widget is currently empty." - not_available: "This widget is not available." + empty: "Bu widget şu anda boş." + not_available: "Bu widget mevcut değil." subitems: title: "Alt öğeler" no_results: "Görüntülenebilir alt öğe yok." view_all_subitems: "Tüm alt öğeleri görüntüle" button_text: "Alt öğe" members: - title: "Members" + title: "Üyeler" no_results: "Görünür üye yok." view_all_members: "Tüm üyeleri göster" - show_members_count: "Show all %{count} members" + show_members_count: "Tüm %{count} üyeleri göster" x_more: - one: "and one more member." + one: "ve bir üye daha" other: "ve %{count} üye daha." news: no_results: "Rapor için yeni birşey bulunamadı." diff --git a/modules/meeting/config/locales/crowdin/af.yml b/modules/meeting/config/locales/crowdin/af.yml index 38d02435437..5addf9ea331 100644 --- a/modules/meeting/config/locales/crowdin/af.yml +++ b/modules/meeting/config/locales/crowdin/af.yml @@ -66,7 +66,9 @@ af: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/ar.yml b/modules/meeting/config/locales/crowdin/ar.yml index f89cef7725c..4e8f5d8a1b6 100644 --- a/modules/meeting/config/locales/crowdin/ar.yml +++ b/modules/meeting/config/locales/crowdin/ar.yml @@ -70,7 +70,9 @@ ar: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/az.yml b/modules/meeting/config/locales/crowdin/az.yml index f2c5312014a..5be20ff2653 100644 --- a/modules/meeting/config/locales/crowdin/az.yml +++ b/modules/meeting/config/locales/crowdin/az.yml @@ -66,7 +66,9 @@ az: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/be.yml b/modules/meeting/config/locales/crowdin/be.yml index e18759192d9..c4379445f48 100644 --- a/modules/meeting/config/locales/crowdin/be.yml +++ b/modules/meeting/config/locales/crowdin/be.yml @@ -68,7 +68,9 @@ be: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/bg.yml b/modules/meeting/config/locales/crowdin/bg.yml index e242f11fa9d..a0181bc7929 100644 --- a/modules/meeting/config/locales/crowdin/bg.yml +++ b/modules/meeting/config/locales/crowdin/bg.yml @@ -66,7 +66,9 @@ bg: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/ca.yml b/modules/meeting/config/locales/crowdin/ca.yml index 0883bfa5886..4c802c3b092 100644 --- a/modules/meeting/config/locales/crowdin/ca.yml +++ b/modules/meeting/config/locales/crowdin/ca.yml @@ -66,7 +66,9 @@ ca: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/ckb-IR.yml b/modules/meeting/config/locales/crowdin/ckb-IR.yml index 3c2d16dadfc..5b3741f976c 100644 --- a/modules/meeting/config/locales/crowdin/ckb-IR.yml +++ b/modules/meeting/config/locales/crowdin/ckb-IR.yml @@ -66,7 +66,9 @@ ckb-IR: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/cs.yml b/modules/meeting/config/locales/crowdin/cs.yml index a659c4c3d96..a94d551f778 100644 --- a/modules/meeting/config/locales/crowdin/cs.yml +++ b/modules/meeting/config/locales/crowdin/cs.yml @@ -68,7 +68,9 @@ cs: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/da.yml b/modules/meeting/config/locales/crowdin/da.yml index ea9f6147f4c..719afd339ea 100644 --- a/modules/meeting/config/locales/crowdin/da.yml +++ b/modules/meeting/config/locales/crowdin/da.yml @@ -66,7 +66,9 @@ da: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/de.yml b/modules/meeting/config/locales/crowdin/de.yml index dba3d01a081..0d4f4111385 100644 --- a/modules/meeting/config/locales/crowdin/de.yml +++ b/modules/meeting/config/locales/crowdin/de.yml @@ -66,7 +66,9 @@ de: errors: models: meeting_participant: - invalid_user: "%{name} ist kein gültiger Teilnehmer." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "Es gibt eine offene Besprechung in der Terminserie, die nicht durch den neuen Zeitplan abgedeckt ist. Passen Sie den Zeitplan an, um alle bestehenden Meetings einzuschließen." diff --git a/modules/meeting/config/locales/crowdin/el.yml b/modules/meeting/config/locales/crowdin/el.yml index bb601239ba1..8db0fc8e2bd 100644 --- a/modules/meeting/config/locales/crowdin/el.yml +++ b/modules/meeting/config/locales/crowdin/el.yml @@ -66,7 +66,9 @@ el: errors: models: meeting_participant: - invalid_user: "Ο χρήστης %{name} δεν είναι έγκυρος συμμετέχων." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/eo.yml b/modules/meeting/config/locales/crowdin/eo.yml index 2bc4b9e3c01..af16b1b8a5a 100644 --- a/modules/meeting/config/locales/crowdin/eo.yml +++ b/modules/meeting/config/locales/crowdin/eo.yml @@ -66,7 +66,9 @@ eo: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/es.yml b/modules/meeting/config/locales/crowdin/es.yml index fe59be3167e..51df23651a3 100644 --- a/modules/meeting/config/locales/crowdin/es.yml +++ b/modules/meeting/config/locales/crowdin/es.yml @@ -66,7 +66,9 @@ es: errors: models: meeting_participant: - invalid_user: "%{name} no es un participante válido." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "Hay una reunión abierta en la serie que no está cubierta por el nuevo calendario. Ajuste el calendario para incluir todas las reuniones existentes." diff --git a/modules/meeting/config/locales/crowdin/et.yml b/modules/meeting/config/locales/crowdin/et.yml index e1ca2a03e87..29fc0abdbb1 100644 --- a/modules/meeting/config/locales/crowdin/et.yml +++ b/modules/meeting/config/locales/crowdin/et.yml @@ -66,7 +66,9 @@ et: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/eu.yml b/modules/meeting/config/locales/crowdin/eu.yml index 22fb6462369..ca9598b5df2 100644 --- a/modules/meeting/config/locales/crowdin/eu.yml +++ b/modules/meeting/config/locales/crowdin/eu.yml @@ -66,7 +66,9 @@ eu: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/fa.yml b/modules/meeting/config/locales/crowdin/fa.yml index c28976e2599..34aebc8ed9a 100644 --- a/modules/meeting/config/locales/crowdin/fa.yml +++ b/modules/meeting/config/locales/crowdin/fa.yml @@ -66,7 +66,9 @@ fa: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/fi.yml b/modules/meeting/config/locales/crowdin/fi.yml index 4a860e467ae..3bb06f0bfef 100644 --- a/modules/meeting/config/locales/crowdin/fi.yml +++ b/modules/meeting/config/locales/crowdin/fi.yml @@ -66,7 +66,9 @@ fi: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/fil.yml b/modules/meeting/config/locales/crowdin/fil.yml index a5749520b7d..9d4e4fd8392 100644 --- a/modules/meeting/config/locales/crowdin/fil.yml +++ b/modules/meeting/config/locales/crowdin/fil.yml @@ -66,7 +66,9 @@ fil: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/fr.yml b/modules/meeting/config/locales/crowdin/fr.yml index c66151ef03f..aec47309040 100644 --- a/modules/meeting/config/locales/crowdin/fr.yml +++ b/modules/meeting/config/locales/crowdin/fr.yml @@ -66,7 +66,9 @@ fr: errors: models: meeting_participant: - invalid_user: "%{name} n'est pas un participant valide." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "Il y a une réunion ouverte dans la série qui n'est pas couverte par le nouvel horaire. Modifiez l'horaire pour inclure toutes les réunions existantes." diff --git a/modules/meeting/config/locales/crowdin/he.yml b/modules/meeting/config/locales/crowdin/he.yml index 60990c45c2d..7cb8adb612a 100644 --- a/modules/meeting/config/locales/crowdin/he.yml +++ b/modules/meeting/config/locales/crowdin/he.yml @@ -68,7 +68,9 @@ he: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/hi.yml b/modules/meeting/config/locales/crowdin/hi.yml index 04d91b0dd42..27ea830748d 100644 --- a/modules/meeting/config/locales/crowdin/hi.yml +++ b/modules/meeting/config/locales/crowdin/hi.yml @@ -66,7 +66,9 @@ hi: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/hr.yml b/modules/meeting/config/locales/crowdin/hr.yml index 6276babc43e..8b2d0f81d5d 100644 --- a/modules/meeting/config/locales/crowdin/hr.yml +++ b/modules/meeting/config/locales/crowdin/hr.yml @@ -67,7 +67,9 @@ hr: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/hu.yml b/modules/meeting/config/locales/crowdin/hu.yml index 5fc6d3b8689..5bf72b76460 100644 --- a/modules/meeting/config/locales/crowdin/hu.yml +++ b/modules/meeting/config/locales/crowdin/hu.yml @@ -66,7 +66,9 @@ hu: errors: models: meeting_participant: - invalid_user: "%{name} nem érvényes résztvevő." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "Az ismétlődő megbeszélésben van egy nyitott megbeszélés, amelyre nem vonatkozik az új ütemezés. Az ütemezésnek az összes létező meeting-et tartalmaznia kell." diff --git a/modules/meeting/config/locales/crowdin/id.yml b/modules/meeting/config/locales/crowdin/id.yml index cc98c075987..273daf36564 100644 --- a/modules/meeting/config/locales/crowdin/id.yml +++ b/modules/meeting/config/locales/crowdin/id.yml @@ -65,7 +65,9 @@ id: errors: models: meeting_participant: - invalid_user: "%{name} bukan peserta yang sah." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "Terdapat satu pertemuan terbuka dalam rangkaian pertemuan yang tidak tercakup dalam jadwal baru. Sesuaikan jadwal untuk mencakup semua pertemuan yang sudah ada." diff --git a/modules/meeting/config/locales/crowdin/it.yml b/modules/meeting/config/locales/crowdin/it.yml index 72618fa981c..668c1deb98b 100644 --- a/modules/meeting/config/locales/crowdin/it.yml +++ b/modules/meeting/config/locales/crowdin/it.yml @@ -66,7 +66,9 @@ it: errors: models: meeting_participant: - invalid_user: "%{name} non è un partecipante valido." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "C'è una riunione aperta nella serie che non è coperta dal nuovo programma. Adatta il programma per includere tutte le riunioni esistenti." diff --git a/modules/meeting/config/locales/crowdin/ja.yml b/modules/meeting/config/locales/crowdin/ja.yml index 2714f9617eb..71e3441c236 100644 --- a/modules/meeting/config/locales/crowdin/ja.yml +++ b/modules/meeting/config/locales/crowdin/ja.yml @@ -65,7 +65,9 @@ ja: errors: models: meeting_participant: - invalid_user: "%{name}は有効な参加者ではありません。" + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "新しいスケジュールではカバーされていない一連の会議があります。すべての会議を含むようにスケジュールを調整してください。" diff --git a/modules/meeting/config/locales/crowdin/ka.yml b/modules/meeting/config/locales/crowdin/ka.yml index ea618cef8b5..a30ef95e73c 100644 --- a/modules/meeting/config/locales/crowdin/ka.yml +++ b/modules/meeting/config/locales/crowdin/ka.yml @@ -66,7 +66,9 @@ ka: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/kk.yml b/modules/meeting/config/locales/crowdin/kk.yml index d9f4b04a4b6..4722bf846ab 100644 --- a/modules/meeting/config/locales/crowdin/kk.yml +++ b/modules/meeting/config/locales/crowdin/kk.yml @@ -66,7 +66,9 @@ kk: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/ko.yml b/modules/meeting/config/locales/crowdin/ko.yml index a3fb2adec5e..7b96cd3493c 100644 --- a/modules/meeting/config/locales/crowdin/ko.yml +++ b/modules/meeting/config/locales/crowdin/ko.yml @@ -65,7 +65,9 @@ ko: errors: models: meeting_participant: - invalid_user: "%{name} 님은 유효한 참가자가 아닙니다." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "이 시리즈에는 새로운 스케줄에 포함되지 않은 오픈 미팅이 하나 있습니다. 모든 기존 미팅을 포함하도록 스케줄을 조정하세요." diff --git a/modules/meeting/config/locales/crowdin/lt.yml b/modules/meeting/config/locales/crowdin/lt.yml index b97615945f7..0f581a41b2e 100644 --- a/modules/meeting/config/locales/crowdin/lt.yml +++ b/modules/meeting/config/locales/crowdin/lt.yml @@ -68,7 +68,9 @@ lt: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/lv.yml b/modules/meeting/config/locales/crowdin/lv.yml index 452cf4d457b..e6dcc5c141f 100644 --- a/modules/meeting/config/locales/crowdin/lv.yml +++ b/modules/meeting/config/locales/crowdin/lv.yml @@ -67,7 +67,9 @@ lv: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/mn.yml b/modules/meeting/config/locales/crowdin/mn.yml index a85cfa17bdd..b9feec45692 100644 --- a/modules/meeting/config/locales/crowdin/mn.yml +++ b/modules/meeting/config/locales/crowdin/mn.yml @@ -66,7 +66,9 @@ mn: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/ms.yml b/modules/meeting/config/locales/crowdin/ms.yml index 9a80f14d84a..26e6f2ca0b7 100644 --- a/modules/meeting/config/locales/crowdin/ms.yml +++ b/modules/meeting/config/locales/crowdin/ms.yml @@ -65,7 +65,9 @@ ms: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/ne.yml b/modules/meeting/config/locales/crowdin/ne.yml index f3745aeaaf7..5c04df5c2c0 100644 --- a/modules/meeting/config/locales/crowdin/ne.yml +++ b/modules/meeting/config/locales/crowdin/ne.yml @@ -66,7 +66,9 @@ ne: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/nl.yml b/modules/meeting/config/locales/crowdin/nl.yml index cc3ad7cfab5..ce75c3639d5 100644 --- a/modules/meeting/config/locales/crowdin/nl.yml +++ b/modules/meeting/config/locales/crowdin/nl.yml @@ -66,7 +66,9 @@ nl: errors: models: meeting_participant: - invalid_user: "%{name} is geen geldige deelnemer." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/no.yml b/modules/meeting/config/locales/crowdin/no.yml index f69e0afa4db..7cd5834d8d5 100644 --- a/modules/meeting/config/locales/crowdin/no.yml +++ b/modules/meeting/config/locales/crowdin/no.yml @@ -66,7 +66,9 @@ errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/pl.yml b/modules/meeting/config/locales/crowdin/pl.yml index e16cb033968..d5c843e878a 100644 --- a/modules/meeting/config/locales/crowdin/pl.yml +++ b/modules/meeting/config/locales/crowdin/pl.yml @@ -68,7 +68,9 @@ pl: errors: models: meeting_participant: - invalid_user: "%{name} nie jest prawidłowym uczestnikiem." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "W serii znajduje się jedno otwarte spotkanie, które nie jest uwzględnione w nowym harmonogramie. Dostosuj harmonogram tak, aby obejmował wszystkie istniejące spotkania." diff --git a/modules/meeting/config/locales/crowdin/pt-BR.yml b/modules/meeting/config/locales/crowdin/pt-BR.yml index 1b7b5b046e2..3830d92a03e 100644 --- a/modules/meeting/config/locales/crowdin/pt-BR.yml +++ b/modules/meeting/config/locales/crowdin/pt-BR.yml @@ -66,7 +66,9 @@ pt-BR: errors: models: meeting_participant: - invalid_user: "%{name} não é um participante válido." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "Há uma reunião aberta na série que não está contemplada na nova programação. Ajuste a programação para incluir todas as reuniões existentes." diff --git a/modules/meeting/config/locales/crowdin/pt-PT.yml b/modules/meeting/config/locales/crowdin/pt-PT.yml index 0399b2474bf..627e6e54605 100644 --- a/modules/meeting/config/locales/crowdin/pt-PT.yml +++ b/modules/meeting/config/locales/crowdin/pt-PT.yml @@ -66,7 +66,9 @@ pt-PT: errors: models: meeting_participant: - invalid_user: "%{name} não é um participante válido." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "Há uma reunião aberta na série que não é abrangida pelo novo calendário. Ajuste o calendário para incluir todas as reuniões existentes." diff --git a/modules/meeting/config/locales/crowdin/ro.yml b/modules/meeting/config/locales/crowdin/ro.yml index 7e483857edc..cd959cc25ba 100644 --- a/modules/meeting/config/locales/crowdin/ro.yml +++ b/modules/meeting/config/locales/crowdin/ro.yml @@ -67,7 +67,9 @@ ro: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "Există o întâlnire deschisă în seria care nu este acoperită de noul program. Reglează programul pentru a include toate întâlnirile existente." diff --git a/modules/meeting/config/locales/crowdin/ru.yml b/modules/meeting/config/locales/crowdin/ru.yml index 7b4f2564a11..9ae9af814ab 100644 --- a/modules/meeting/config/locales/crowdin/ru.yml +++ b/modules/meeting/config/locales/crowdin/ru.yml @@ -68,7 +68,9 @@ ru: errors: models: meeting_participant: - invalid_user: "%{name} не является допустимым участником." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "В серии есть одно открытое совещание, которое не включено в новый график. Измените график, чтобы включить все существующие совещания." @@ -625,9 +627,9 @@ ru: text_agenda_item_duplicate_in_next_meeting: "Вы уверены, что хотите добавить копию этого пункта повестки дня к следующему совещанию на %{date} в %{time}? Результаты не будут дублироваться." text_agenda_item_duplicated_in_next_meeting: "Пункт повестки дня дублируется на следующем совещании %{date}" text_work_package_has_no_upcoming_meeting_agenda_items: "Этот пакет работ пока не включен в повестку предстоящего совещания." - text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." - text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." - text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." + text_agenda_item_no_available_occurrence: "Все предстоящие события были отменены." + text_agenda_item_dialog_skipping_cancelled_one: "Примечание: Пропуск отмененного события на %{date}." + text_agenda_item_dialog_skipping_cancelled_many: "Примечание: Пропуск %{count} отмененных событий." text_work_package_add_to_meeting_hint: 'Используйте кнопку "Добавить к совещанию", чтобы добавить этот пакет работ к предстоящему совещанию.' text_work_package_has_no_past_meeting_agenda_items: "Этот рабочий пакет не был добавлен в качестве пункта повестки дня на прошлом совещании." text_email_updates_muted: "Обновления календаря по электронной почте отключены. Участники не будут получать обновленные приглашения по электронной почте, когда Вы вносите изменения." diff --git a/modules/meeting/config/locales/crowdin/rw.yml b/modules/meeting/config/locales/crowdin/rw.yml index 505e09f02f5..f9942630d6b 100644 --- a/modules/meeting/config/locales/crowdin/rw.yml +++ b/modules/meeting/config/locales/crowdin/rw.yml @@ -66,7 +66,9 @@ rw: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/si.yml b/modules/meeting/config/locales/crowdin/si.yml index 708c52dcc5f..57af020277b 100644 --- a/modules/meeting/config/locales/crowdin/si.yml +++ b/modules/meeting/config/locales/crowdin/si.yml @@ -66,7 +66,9 @@ si: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/sk.yml b/modules/meeting/config/locales/crowdin/sk.yml index d2e48c8b589..4facaca7d8e 100644 --- a/modules/meeting/config/locales/crowdin/sk.yml +++ b/modules/meeting/config/locales/crowdin/sk.yml @@ -68,7 +68,9 @@ sk: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/sl.yml b/modules/meeting/config/locales/crowdin/sl.yml index 9335c1bbf44..f17e06c1b8d 100644 --- a/modules/meeting/config/locales/crowdin/sl.yml +++ b/modules/meeting/config/locales/crowdin/sl.yml @@ -68,7 +68,9 @@ sl: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/sr.yml b/modules/meeting/config/locales/crowdin/sr.yml index da5eb20542f..322ddc8286e 100644 --- a/modules/meeting/config/locales/crowdin/sr.yml +++ b/modules/meeting/config/locales/crowdin/sr.yml @@ -67,7 +67,9 @@ sr: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/sv.yml b/modules/meeting/config/locales/crowdin/sv.yml index da57a3a8d2f..f34fe5e6e9e 100644 --- a/modules/meeting/config/locales/crowdin/sv.yml +++ b/modules/meeting/config/locales/crowdin/sv.yml @@ -66,7 +66,9 @@ sv: errors: models: meeting_participant: - invalid_user: "%{name} är inte en giltig deltagare." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "Det finns ett öppet möte i serien som inte omfattas av det nya schemat. Justera schemat för att inkludera alla befintliga möten." diff --git a/modules/meeting/config/locales/crowdin/th.yml b/modules/meeting/config/locales/crowdin/th.yml index 7e9b8c26d34..7fbc6287100 100644 --- a/modules/meeting/config/locales/crowdin/th.yml +++ b/modules/meeting/config/locales/crowdin/th.yml @@ -65,7 +65,9 @@ th: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/tr.yml b/modules/meeting/config/locales/crowdin/tr.yml index aea81ff1d70..e2b28f88f6a 100644 --- a/modules/meeting/config/locales/crowdin/tr.yml +++ b/modules/meeting/config/locales/crowdin/tr.yml @@ -62,11 +62,13 @@ tr: meeting_participant: invited: "Davet edilen" attended: "Katıldığım" - participation_status: "Participation status" + participation_status: "Katılımcı durumu" errors: models: meeting_participant: - invalid_user: "%{name} geçerli bir katılımcı değildir." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "Seride yeni zamanlamaya uymayan ve açık bir toplantı bulunmakta. Mevcut toplantıları kapsaması için zamanlayın." @@ -115,7 +117,7 @@ tr: label_meeting_new_dynamic: "Tek seferlik yeni toplantı" label_meeting_new_recurring: "Yeni yinelenen toplantı" label_meeting_create: "Toplantı oluştur" - label_meeting_duplicate: "Duplicate meeting" + label_meeting_duplicate: "Toplantıyı çoğalt" label_meeting_edit: "Toplantıyı Düzenle" label_meeting_agenda: "Gündem" label_meeting_minutes: "Dakika" @@ -127,8 +129,8 @@ tr: label_meeting_date_time: "Tarih/Saat" label_meeting_date_and_time: "Tarih ve zaman" label_meeting_diff: "Fark" - label_meeting_send_updates: "Send email calendar invite and updates" - label_meeting_send_updates_caption: "Send out email invites to all participants of this meeting" + label_meeting_send_updates: "E-posta takvim daveti ve güncellemeleri gönderin" + label_meeting_send_updates_caption: "Bu toplantının tüm katılımcılarına e-posta davetiyeleri gönderin" label_recurring_meeting: "Yinelenen toplantı" label_recurring_meeting_part_of: "Toplantı serisinin bir parçası" label_recurring_meeting_new: "Yeni yinelenen toplantı" @@ -136,7 +138,7 @@ tr: label_template: "Şablon" label_recurring_meeting_view: "Toplantı serisini görüntüle" label_recurring_meeting_create: "Açık" - label_recurring_meeting_duplicate: "Duplicate as a one-time meeting" + label_recurring_meeting_duplicate: "Tek seferlik toplantı olarak çoğaltın" label_recurring_meeting_cancel: "Bu etklinliği iptal et" label_recurring_meeting_delete: "İptal et" label_recurring_meeting_restore: "Bu toplantıyı geri al" @@ -219,7 +221,7 @@ tr: open_my_meetings_link: "Toplantılarıma git" series: title: "[%{project_name}] Toplantı serisi '%{title}'" - summary: "%{actor} has invited you to a new meeting series '%{title}'" + summary: "%{actor} sizi yeni bir toplantı serisine davet etti '%{title}'" series_updated: title: "[%{project_name}] Toplantı serisi '%{title}' güncellendi" summary: "'%{title}' toplantı serisi %{actor} tarafından güncellendi" @@ -231,29 +233,29 @@ tr: header: "İptal edildi: Toplantı '%{title}'" header_occurrence: "İptal edildi: Toplantı '%{title}'" header_series: "İptal edildi: Toplantı serisi '%{title}'" - summary_occurrence: "An occurrence of '%{title}' has been cancelled by %{actor}, or you have been removed as a participant" - summary_series: "Meeting series '%{title}' has been cancelled by %{actor}, or you have been removed as a participant" - summary: "'%{title}' has been cancelled by %{actor}, or you have been removed as a participant" + summary_occurrence: "Bir '%{title}' oluşumu %{actor}tarafından iptal edildi veya katılımcı olarak çıkarıldınız" + summary_series: "Toplantı serisi '%{title}' %{actor}tarafından iptal edildi veya katılımcı olarak çıkarıldınız" + summary: "'%{title}' %{actor}tarafından iptal edildi veya katılımcı olarak çıkarıldınız" date_time: "Planlanan tarih/saat" participant_added: - header: "Meeting '%{title}' - Participant added" - header_series: "Meeting series '%{title}' - Participant added" - summary: "%{actor} added %{participant} to the meeting '%{title}'" - summary_series: "%{actor} added %{participant} to the meeting series '%{title}'" + header: "Toplantı '%{title}' - Katılımcı eklendi" + header_series: "Toplantı serisi '%{title}' - Katılımcı eklendi" + summary: "%{actor} toplantıya %{participant} eklendi '%{title}'" + summary_series: "%{actor} '%{title}' toplantı serisine %{participant} eklendi" participant_removed: - header: "Meeting '%{title}' - Participant removed" - header_series: "Meeting series '%{title}' - Participant removed" - summary: "%{actor} removed %{participant} from the meeting '%{title}'" - summary_series: "%{actor} removed %{participant} from the meeting series '%{title}'" + header: "Toplantı '%{title}' - Katılımcı kaldırıldı" + header_series: "Toplantı serisi '%{title}' - Katılımcı kaldırıldı" + summary: "%{actor} %{participant} adresini toplantıdan kaldırdı '%{title}'" + summary_series: "%{actor} %{participant} adresini '%{title}' toplantı serisinden kaldırdı" ended: - header_series: "Ended: Meeting series '%{title}'" - summary_series: "Meeting series '%{title}' has been ended by %{actor}" + header_series: "Sona erdi: Toplantı serisi '%{title}'" + summary_series: "'%{title}' toplantısı serisi %{actor} tarafından sonlandırıldı." updated: header: "'%{title}' toplantısı güncellendi" summary: "'%{title}' toplantısı %{actor} tarafından güncellendi" body: "'%{title}' toplantısı %{actor} tarafından güncellendi." - old_title: "Old title" - new_title: "New title" + old_title: "Eski başlık" + new_title: "Yeni başlık" old_date_time: "Eski tarih/saat" new_date_time: "Yeni tarih/saat" old_location: "Eski konum" @@ -279,8 +281,8 @@ tr: Şablonda yer almayan tüm toplantı bilgileri kaybolacaktır. Devam etmek istiyor musunuz? confirm_button: "Bu toplantıyı iptal et" blankslate: - title: "There are no meetings to display" - desc: "You can create a new meeting or change filter criteria" + title: "Gösterilecek toplantı yok" + desc: "Yeni bir toplantı oluşturabilir veya filtre kriterlerini değiştirebilirsiniz" label_export_pdf: "PDF olarak dışa aktar" export: your_meeting_export: "Toplantı dışa aktarılıyor" @@ -300,13 +302,13 @@ tr: caption: Varsayılan şablon çoğu toplantı için uygundur ve mevcut durumu temsil eder. minutes: label: Notlar - caption: The minutes template is suitable for closed and archived meetings. + caption: Tutanak şablonu kapalı ve arşivlenmiş toplantılar için uygundur. first_page_header_left: label: İlk sayfa başlığı sol caption: Bu metin ilk sayfada başlığın solunda görünecektir. author: label: Sahibi - caption: The author of the minutes will be displayed in the subtitle. + caption: Tutanağın yazarı alt başlıkta görüntülenecektir. include_participants: label: Katılımcı listesini içer caption: Toplantı gündeminin üstünde katılımcıların listesi gösterilecektir. @@ -336,7 +338,7 @@ tr: disable: "E-posta takvim güncellemeleri devre dışı bırakılsın mı?" message: enable: > - All participants will receive updated calendar invites via email every time there is a change to the meeting date, time, location or participants. Once enabled, an email will be sent out immediately to all participants. + Toplantı tarihi, saati, konumu veya katılımcılarında herhangi bir değişiklik olduğunda, tüm katılımcılara e-posta yoluyla güncellenmiş takvim davetleri gönderilir. Bu özellik etkinleştirildiğinde, tüm katılımcılara anında bir e-posta gönderilir. disable: > Toplantı tarihi, saati, yeri veya katılımcılarında değişiklik olduğunda katılımcılar artık e-posta yoluyla güncellenmiş takvim davetleri almayacaktır. Bu toplantı için zaten bir davetleri varsa, artık doğru olmayabilir. confirm_label: @@ -345,37 +347,37 @@ tr: banner: participants: enabled: > - All participants will receive updated calendar invites via email when you add or remove participants. + Katılımcı eklediğinizde veya çıkardığınızda tüm katılımcılar e-posta yoluyla güncellenmiş takvim davetiyeleri alacaktır. disabled: > - Email calendar updates are disabled. Participants will not receive an email informing them when you add or remove participants. + E-posta takvim güncellemeleri devre dışı bırakılmıştır. Katılımcılar, katılımcı eklediğinizde veya çıkardığınızda onları bilgilendiren bir e-posta almayacaktır. onetime: enabled: > - All participants will receive updated calendar invites via email when you add or remove participants. + Katılımcı eklediğinizde veya çıkardığınızda tüm katılımcılar e-posta yoluyla güncellenmiş takvim davetiyeleri alacaktır. disabled: > - Participants will not receive an email informing them of changes to meeting date, time or participants. + Katılımcılara toplantı tarihi, saati veya katılımcılarla ilgili değişiklikleri bildiren bir e-posta gönderilmeyecektir. occurrence: enabled: > - Email calendar updates are enabled for the meeting series. All participants will receive updated calendar invites informing them of your changes to this occurrence. + disabled: > - Email calendar updates are disabled for the meeting series. Participants will not receive an email informing them of your changes to this occurrence. + Toplantı serisi için e-posta takvimi güncellemeleri devre dışı bırakılmıştır. Katılımcılar, bu olayda yaptığınız değişiklikleri bildiren bir e-posta almayacaktır. template: enabled: > - Email calendar updates are enabled for the meeting series. All participants will receive updated calendar invites informing them of your changes to this template or to individual occurrences. + Toplantı serisi için e-posta takvimi güncellemeleri etkinleştirilmiştir. Tüm katılımcılar, bu şablonda veya münferit olaylarda yaptığınız değişiklikleri bildiren güncellenmiş takvim davetiyeleri alacaktır. disabled: > - Email calendar updates are disabled for the meeting series. Participants will not receive an email informing them of your changes to this template or to individual occurrences. + Toplantı serisi için e-posta takvimi güncellemeleri devre dışı bırakılmıştır. Katılımcılar, bu şablonda veya münferit olaylarda yaptığınız değişiklikleri bildiren bir e-posta almayacaktır. presentation_mode: - title: "Presentation Mode" - button_present: "Present" - exit: "Exit presentation" - current_item: "Current item" - total_items: "%{current} of %{total}" - previous: "Previous" - next: "Next" - no_items: "No agenda items" - no_items_flash: "There are no agenda items to present." + title: "Sunum modu" + button_present: "Sunumu başlat" + exit: "Sunumdan çık" + current_item: "Geçerli öğe" + total_items: "%{current} / %{total}" + previous: "Önceki" + next: "Sonraki" + no_items: "Gündem maddesi yok" + no_items_flash: "Sunulacak herhangi bir gündem maddesi bulunmamaktadır." ical_response: - update_failed: "Could not update participation status." - meeting_not_found: "Meeting not found for the given UID." + update_failed: "Katılım durumu güncellenemedi." + meeting_not_found: "Verilen UID için toplantı bulunamadı." meeting_section: untitled_title: "İsimsiz bölüm" delete_confirmation: "Bölümün silinmesi tüm gündem maddelerini de silecektir. Bunu yapmak istediğinizden emin misiniz?" @@ -383,16 +385,16 @@ tr: empty_text: "Maddeleri buraya sürükleyin ya da yeni bir tane oluşturun" meeting_participant: participation_status: - needs_action: "No response" - accepted: "Accepted" - declined: "Declined" - tentative: "Maybe" - unknown: "Unknown" + needs_action: "Yanıt yok" + accepted: "Kabul edildi" + declined: "Reddedildi" + tentative: "Belki" + unknown: "Bilinmeyen" recurring_meeting: time_zone_difference_banner: title: "Saat dilimi farkı" description: > - The dates below are referencing the time zone of the meeting series (%{actual_zone}), not your local time zone (%{user_zone}). + Aşağıda gösterilen tarihler, sizin yerel saat diliminize (%{user_zone}) göre değil, toplantı serisinin saat dilimine (%{actual_zone}) göre hesaplanmıştır. ended_blankslate: title: "Toplantı serisi sona erdi" message: "Bu toplantı serisi sona ermiştir. Gelecek toplantı yoktur. " @@ -411,9 +413,9 @@ tr: label_view_template: "Şablonu görüntüle" label_edit_template: "Şablonu düzenle" banner_html: > - You are currently editing a template of a meeting series: %{link}. Every new occurrence of a meeting in the series will use this template. Changes will not affect past or already-created meetings. + Şu anda bir toplantı serisinin şablonunu düzenliyorsunuz: %{link}. Serideki bir toplantının her yeni oluşumu bu şablonu kullanacaktır. Değişiklikler geçmiş veya önceden oluşturulmuş toplantıları etkilemeyecektir. draft_banner_html: > - You are currently editing a template of a meeting series: %{link}. Every new occurrence of a meeting in the series will use this template. Changes will not affect past or already-created meetings. No email invites will be sent in this current draft mode until you open the first meeting. + Şu anda bir toplantı serisinin şablonunu düzenliyorsunuz: %{link}. Serideki bir toplantının her yeni oluşumu bu şablonu kullanacaktır. Değişiklikler geçmiş veya önceden oluşturulmuş toplantıları etkilemeyecektir. Siz ilk toplantıyı açana kadar bu geçerli taslak modunda hiçbir e-posta daveti gönderilmeyecektir. frequency: x_daily: one: "Her gün" @@ -524,27 +526,27 @@ tr: label_agenda_item_move_to_next: "Sonraki toplantıya taşı" label_agenda_item_move_to_backlog: "Birikmiş iş listesine taşı" label_agenda_item_move_to_current_meeting: "Şuanki toplantıya taşı" - label_agenda_item_move_to_section: "Move to section" + label_agenda_item_move_to_section: "Bölüme taşı" label_agenda_item_move_to_top: "En üste taşı" label_agenda_item_move_to_bottom: "En aşağı taşı" label_agenda_item_move_up: "Yukarı taşı" label_agenda_item_move_down: "Aşağı taşı" - label_agenda_item_duplicate: "Duplicate" - label_agenda_item_duplicate_in_next: "Duplicate in next meeting" - label_agenda_item_duplicate_in_next_title: "Duplicate in next meeting?" + label_agenda_item_duplicate: "Çoğalt" + label_agenda_item_duplicate_in_next: "Bir sonraki toplantıda tekrarlayın" + label_agenda_item_duplicate_in_next_title: "Bir sonraki toplantıda tekrarlansın mı?" label_agenda_item_add_notes: "Not ekle" label_agenda_item_add_outcome: "Sonuç ekle" label_agenda_item_work_package_add: "İş paketi ekle" label_agenda_item_work_package: "Gündem maddesinin iş parçası" label_section_rename: "Seçimi yeniden adlandır" label_agenda_outcome: "Sonuçlar" - label_agenda_new_outcome: "New outcome" + label_agenda_new_outcome: "Yeni sonuç" label_agenda_outcome_actions: "Gündem sonuçları eylemleri" label_agenda_outcome_edit: "Sonuç düzenle" label_agenda_outcome_delete: "Sonuç kaldır" - label_added_as_outcome: "Added as outcome" - label_write_outcome: "Write outcome" - label_existing_work_package: "Existing work package" + label_added_as_outcome: "Sonuç olarak eklendi" + label_write_outcome: "Sonuç yazın" + label_existing_work_package: "Mevcut iş paketi" text_outcome_not_editable_anymore: "Bu sonuç artık düzenlenemez." text_outcome_cannot_be_added: "Artık gündem maddesi sonucu eklenemez." label_backlog_clear: "Birikmiş işleri temizle" @@ -570,7 +572,7 @@ tr: label_meeting_series_details: "Toplantı serisi detayları" label_meeting_details_edit: "Toplantı detaylarını düzenle" label_meeting_state: "Toplantı durumu" - label_meeting_state_draft: "Draft" + label_meeting_state_draft: "Taslak" label_meeting_state_open: "Açık" label_meeting_state_closed: "Kapalı" label_meeting_state_agenda_created: "Gündem oluşturuldu" @@ -581,26 +583,26 @@ tr: label_meeting_reopen_action: "Toplantıyı tekrar aç" label_meeting_close_action: "Toplantıyı kapat" label_meeting_in_progress_action: "Toplantıya başla" - label_meeting_open_action: "Open meeting" - text_meeting_draft_description: "Prepare your agenda in draft mode. This meeting will not send out any calendar updates or invites, even if you change meeting details or add/remove participants." - text_meeting_open_description: "You can add/remove agenda items and participants. Once the agenda is ready, mark it as in progress to document outcomes." + label_meeting_open_action: "Toplantıyı açın" + text_meeting_draft_description: "Gündeminizi taslak modunda hazırlayın. Toplantı ayrıntılarını değiştirseniz veya katılımcı ekleyip çıkarsanız bile bu toplantı herhangi bir takvim güncellemesi veya davet göndermeyecektir." + text_meeting_open_description: "Bu toplantı açık. Gündem maddesi ekleyip kaldırabilirsiniz ve istediğiniz gibi düzenleyebilirsiniz. Gündem hazır olduğunda sonuçları belgelemek için devam etmekte olarak işaretleyin." text_meeting_closed_description: "Toplantı kapatılmış. Artık gündem maddesi ekleyip çıkartamazsınız." - text_meeting_in_progress_description: "You can modify the agenda, document outcomes for each item and track attendance for participants. Once the meeting is complete, you can mark it as closed to lock it." + text_meeting_in_progress_description: "Gündemi değiştirebilir, her madde için sonuçları belgeleyebilir ve katılımcıların katılımını takip edebilirsiniz. Toplantı tamamlandığında, kilitlemek için kapalı olarak işaretleyebilirsiniz." text_meeting_open_dropdown_description: "Mevcut sonuçlar kalacak ama kullanıcılar yeni sonuçlar ekleyemeyecekler." text_meeting_in_progress_dropdown_description: "Toplantı sırasında alınan kararları ya da bilgi gereksinimleri sonuçlarını belgeleyin." text_meeting_closed_dropdown_description: "Bu toplantı kapalı. Gündem maddelerini ya da sonuçları artık düzenleyemezsiniz." - text_meeting_draft_banner: "You are currently in draft mode. This meeting will not send out any calendar updates or invites, even if you change meeting details or add/remove participants." - text_exit_draft_mode_dialog_title: "Open this meeting and send invites?" - text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." - text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" - text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + text_meeting_draft_banner: "Şu anda taslak modundasınız. Toplantı ayrıntılarını değiştirseniz veya katılımcı ekleyip çıkarsanız bile bu toplantı herhangi bir takvim güncellemesi veya davet göndermeyecektir." + text_exit_draft_mode_dialog_title: "Bu toplantıyı açıp davetiye gönderelim mi?" + text_exit_draft_mode_dialog_subtitle: "Bir toplantı planladıktan sonra taslak moduna geri dönemezsiniz." + text_exit_draft_mode_dialog_template_title: "Bu toplantı serisinin ilkini açar mısınız?" + text_exit_draft_mode_dialog_template_subtitle: "Bundan sonra taslak moduna geri dönemezsiniz." text_meeting_not_editable_anymore: "Bu toplantıyı artık düzenleyemezsiniz." text_meeting_not_present_anymore: "Toplantı silinmiş. Başka bir toplantı seçin lütfen." label_add_work_package_to_meeting_dialog_title: "Toplantı seç" label_add_work_package_to_meeting_section_label: "Bölüm" label_add_work_package_to_meeting_dialog_button: "Toplantıya ekle" label_meeting_selection_caption: "Bu iş paketini sadece yaklaşan veya devam eden bir toplantıya eklemek mümkündür." - label_section_selection_caption: "Choose a particular section of the agenda or add it to the backlog." + label_section_selection_caption: "Gündemin belirli bir bölümünü seçin veya biriktirme listesine ekleyin." placeholder_section_select_meeting_first: "Önce toplantı seçimi gereklidir" text_add_work_package_to_meeting_form: "İş paketi, seçilen toplantıya veya birikime gündem maddesi olarak eklenecektir." text_add_work_package_to_meeting_description: "Bir iş paketi tartışılmak üzere bir veya birden fazla toplantıya eklenebilir. Bununla ilgili tüm notlar da burada görülebilir." @@ -608,40 +610,40 @@ tr: text_agenda_item_not_editable_anymore: "Bu takvim maddesi artık düzenlenemez." text_agenda_item_move_next_meeting: "Bu madde %{date} tarihindeki %{time} saatli bir sonraki toplantıya taşınacaktır." text_agenda_item_moved_to_next_meeting: "Gündem maddesi %{date} tarihli sonraki toplantıya taşındı" - text_agenda_item_duplicate_in_next_meeting: "Are you sure you want to add a copy of this agenda item to the next meeting, on %{date} at %{time}? Outcomes will not be duplicated." - text_agenda_item_duplicated_in_next_meeting: "Agenda item duplicated in the next meeting, on %{date}" + text_agenda_item_duplicate_in_next_meeting: "%{time}Bu gündem maddesinin bir kopyasını %{date} adresindeki bir sonraki toplantıya eklemek istediğinizden emin misiniz? Sonuçlar çoğaltılmayacaktır." + text_agenda_item_duplicated_in_next_meeting: "Gündem maddesi bir sonraki toplantıda tekrarlandı, %{date}adresinde" text_work_package_has_no_upcoming_meeting_agenda_items: "Bu iş paketi henüz yaklaşan bir toplantı gündemine alınmamıştır." - text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." - text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." - text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." + text_agenda_item_no_available_occurrence: "Yaklaşan tüm etkinlikler iptal edilmiştir." + text_agenda_item_dialog_skipping_cancelled_one: "Not: %{date} tarihindeki iptal edilmiş olayları atla." + text_agenda_item_dialog_skipping_cancelled_many: "Not: İptal edilen %{count} olayları atla." text_work_package_add_to_meeting_hint: 'Bu iş paketini yaklaşan bir toplantıya eklemek için "Toplantıya ekle" düğmesini kullanın.' - text_work_package_has_no_past_meeting_agenda_items: "This work package was not added as an agenda item in a past meeting." - text_email_updates_muted: "Email calendar updates are muted. Participants will not receive updated invites via email when you make changes." - text_email_updates_enabled: "Email calendar updates are enabled. All participants will receive updated invites via email when you make changes." + text_work_package_has_no_past_meeting_agenda_items: "Bu çalışma paketi geçmiş bir toplantıda gündem maddesi olarak eklenmemiştir." + text_email_updates_muted: "E-posta takvim güncellemeleri kapatılmıştır. Değişiklik yaptığınızda katılımcılar e-posta yoluyla güncellenmiş davetler almayacaktır." + text_email_updates_enabled: "E-posta takvim güncellemeleri etkinleştirildi. Değişiklik yaptığınızda tüm katılımcılar e-posta yoluyla güncellenmiş davetiyeler alacaktır." my_account: access_tokens: token/ical_meeting: - blank_description: "You can create one using the button below." - blank_title: "No iCalendar meeting token" + blank_description: "Aşağıdaki düğmeyi kullanarak bir tane oluşturabilirsiniz." + blank_title: "iCalendar toplantı belirteci yok" title: "Toplantılar için iCalendar" - table_title: "iCalendar meeting tokens" - text_hint: "iCalendar meeting tokens allow users to subscribe to all their meetings and view up-to-date meeting information in external clients." - disabled_text: "iCalendar meeting subscriptions are not enabled by the administrator. Please contact your administrator to use this feature." + table_title: "iCalendar toplantı belirteçleri" + text_hint: "iCalendar toplantı belirteçleri, kullanıcıların tüm toplantılarına abone olmalarını ve harici istemcilerde güncel toplantı bilgilerini görüntülemelerini sağlar." + disabled_text: "iCalendar toplantı abonelikleri yönetici tarafından etkinleştirilmemiştir. Bu özelliği kullanmak için lütfen yöneticinize başvurun." add_button: "Takvime abone ol" my: access_token: dialog: token/ical_meeting: - dialog_title: "New iCal subscription token for meetings" - dialog_body: "This token will generate an iCal subscription URL that lets you view all your meetings in an external calendar application." + dialog_title: "Toplantılar için yeni iCal abonelik belirteci" + dialog_body: "Bu belirteç, tüm toplantılarınızı harici bir takvim uygulamasında görüntülemenizi sağlayan bir iCal abonelik URL'si oluşturacaktır." create_button: "Abonelik oluştur" name_label: "Anahtar adı" name_caption: '"Telefonum" veya "İş bilgisayarım" gibi kullanacağınız yerin adını verebilirsiniz.' created_dialog: token/ical_meeting: title: "Bir iCal toplantı abonelik anahtarı oluşturuldu" - body: "Treat the following URL as you would a password. Anyone who has access to it can view all your meetings." + body: "Aşağıdaki URL'ye bir parola gibi davranın. Buna erişimi olan herkes tüm toplantılarınızı görüntüleyebilir." revocation: token/ical_meeting: - notice_success: "The iCalendar meeting subscription has been revoked successfully." - notice_failure: "Failed to revoke iCalendar meeting subscription: %{error}" + notice_success: "iCalendar toplantı aboneliği başarıyla iptal edildi." + notice_failure: "iCalendar toplantı aboneliği iptal edilemedi: %{error}" diff --git a/modules/meeting/config/locales/crowdin/uk.yml b/modules/meeting/config/locales/crowdin/uk.yml index 330ca5d4bd6..121b62fba93 100644 --- a/modules/meeting/config/locales/crowdin/uk.yml +++ b/modules/meeting/config/locales/crowdin/uk.yml @@ -68,7 +68,9 @@ uk: errors: models: meeting_participant: - invalid_user: "%{name} не є дійсним учасником." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "Одна відкрита нарада в серії не входить у новий розклад. Налаштуйте розклад так, щоб він охоплював усі наявні наради." diff --git a/modules/meeting/config/locales/crowdin/uz.yml b/modules/meeting/config/locales/crowdin/uz.yml index 2a1b63ab9ef..9326b37e3d0 100644 --- a/modules/meeting/config/locales/crowdin/uz.yml +++ b/modules/meeting/config/locales/crowdin/uz.yml @@ -66,7 +66,9 @@ uz: errors: models: meeting_participant: - invalid_user: "%{name} is not a valid participant." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "There is one open meeting in the series that is not covered by the new schedule. Adjust the schedule to include all existing meetings." diff --git a/modules/meeting/config/locales/crowdin/vi.yml b/modules/meeting/config/locales/crowdin/vi.yml index 0ba332b18df..5ba8bb4e92b 100644 --- a/modules/meeting/config/locales/crowdin/vi.yml +++ b/modules/meeting/config/locales/crowdin/vi.yml @@ -65,7 +65,9 @@ vi: errors: models: meeting_participant: - invalid_user: "%{name} không phải là người tham gia hợp lệ." + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "Có một cuộc họp mở trong chuỗi không có trong lịch trình mới. Điều chỉnh lịch trình để bao gồm tất cả các cuộc họp hiện có." diff --git a/modules/meeting/config/locales/crowdin/zh-CN.yml b/modules/meeting/config/locales/crowdin/zh-CN.yml index e2c80797e4c..9edf1f92c7d 100644 --- a/modules/meeting/config/locales/crowdin/zh-CN.yml +++ b/modules/meeting/config/locales/crowdin/zh-CN.yml @@ -65,7 +65,9 @@ zh-CN: errors: models: meeting_participant: - invalid_user: "%{name} 不是有效的参与者。" + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "该系列会议中有一次公开会议未列入新时间表。请调整时间表,将会议包含在内。" diff --git a/modules/meeting/config/locales/crowdin/zh-TW.yml b/modules/meeting/config/locales/crowdin/zh-TW.yml index 1dd793057e8..7520cef732d 100644 --- a/modules/meeting/config/locales/crowdin/zh-TW.yml +++ b/modules/meeting/config/locales/crowdin/zh-TW.yml @@ -65,7 +65,9 @@ zh-TW: errors: models: meeting_participant: - invalid_user: "%{name} 不是有效的參與者。" + user_invalid: "is not a valid participant." + meeting_agenda_item: + user_invalid: "is not a valid participant." recurring_meeting: must_cover_existing_meetings: one: "該系列中有一個公開會議未包含在新的時間表中。調整時間表以包含所有現有會議。" diff --git a/modules/openid_connect/config/locales/crowdin/tr.yml b/modules/openid_connect/config/locales/crowdin/tr.yml index e63fc3630b1..3b4f8fd1106 100644 --- a/modules/openid_connect/config/locales/crowdin/tr.yml +++ b/modules/openid_connect/config/locales/crowdin/tr.yml @@ -14,7 +14,7 @@ tr: display_name: Ekran adı client_id: İstemci ID client_secret: İstemci gizliliği - groups_claim: Groups claim + groups_claim: Grupları al group_regexes: Kalıplar (düzenli ifadeler) secret: Gizli Anahtar scope: Kapsam @@ -43,10 +43,10 @@ tr: response_is_not_successful: " %{status} ile yanıt verir." response_is_not_json: " JSON gövdesini cevap olarak vermez." response_misses_required_attributes: " gerekli öznitelikleri döndürmez. Eksik öznitelikler: %{missing_attributes}." - invalid_claims_essential: "does not define a boolean at %{attribute}." - invalid_claims_location: "contain unsupported locations: %{invalid}. Supported locations are: %{supported}." - invalid_claims_values: "does not define an array at %{attribute}." - non_object_attribute: "does not define a JSON object at %{attribute}." + invalid_claims_essential: "%{attribute}adresinde bir boolean tanımlamaz." + invalid_claims_location: "desteklenmeyen konumları içerir: %{invalid}. Desteklenen konumlar şunlardır: %{supported}." + invalid_claims_values: "%{attribute}adresinde bir dizi tanımlamaz." + non_object_attribute: "%{attribute}adresinde bir JSON nesnesi tanımlamaz." provider: delete_warning: input_delete_confirmation: Silme işlemini onaylamak için sağlayıcı %{name} ismini girin. @@ -67,21 +67,21 @@ tr: title: Çıkarılan grup adları placeholder_none: Şu anda kalıpla eşleşen grup yok. match_preview_dialog_component: - description: This allows you to test your regular expression pattern against sample data to verify that the group names you want extracted are indeed extracted properly. You can modify your pattern here but the test string and matches themselves will not be saved with the configuration. - group_names_label: Test group names - group_names_description: Enter one group name per line as it would appear in the OIDC claim, to see how your regular expression patterns affect them. - show_button: Test pattern - title: Test regular expressions + description: Bu, çıkarılmasını istediğiniz grup adlarının gerçekten düzgün bir şekilde çıkarıldığını doğrulamak için düzenli ifade deseninizi örnek verilerle test etmenize olanak tanır. Deseninizi burada değiştirebilirsiniz ancak test dizesi ve eşleşmeler yapılandırmayla birlikte kaydedilmeyecektir. + group_names_label: Test grubu adları + group_names_description: Düzenli ifade kalıplarınızın bunları nasıl etkilediğini görmek için her satıra OIDC talebinde göründüğü gibi bir grup adı girin. + show_button: Test deseni + title: Düzenli ifadeleri test et instructions: endpoint_url: OpenID Connect sağlayıcısı tarafından size verilen uç nokta URL'si - group_regexes: One regular expression pattern per line. Please read [the documentation](docs_url) for more information on how to do this. - group_regexes_detail: Optionally define patterns using regular expressions to match group names from the groups claim. For example, entering a pattern like ^Level1/Level2/([\w/]+)$ will extract the group “Groupname/SubGroup” from a response including “Level1/Level2/GroupName/SubGroup”. If no pattern matches, the group is ignored. Leave empty to synchronize all groups using their full group name. - group_regexes_testing: "Click on 'Test pattern' to test your regular expression pattern against sample OIDC userinfo." - group_sync: Automatically extract and synchronise group membership information from the identity provider. - groups_claim: Specify the name of the claim that's expected to indicate the group names that the user is a member of. + group_regexes: Satır başına bir düzenli ifade kalıbı. Bunun nasıl yapılacağı hakkında daha fazla bilgi için lütfen [belgeleri](docs_url) okuyun. + group_regexes_detail: İsteğe bağlı olarak, gruplar talebinden grup adlarını eşleştirmek için düzenli ifadeler kullanarak kalıplar tanımlayın. Örneğin, ^Level1/Level2/([\w/]+)$ gibi bir kalıp girilmesi, "Level1/Level2/GroupName/SubGroup" içeren bir yanıttan "Groupname/SubGroup" grubunu çıkaracaktır. Hiçbir kalıp eşleşmezse, grup yok sayılır. Tüm grupları tam grup adlarını kullanarak senkronize etmek için boş bırakın. + group_regexes_testing: "Düzenli ifade kalıbınızı örnek OIDC kullanıcı bilgilerine karşı test etmek için 'Kalıbı test et' seçeneğine tıklayın." + group_sync: Grup üyeliği bilgilerini kimlik sağlayıcısından otomatik olarak alın ve senkronize edin. + groups_claim: Kullanıcının üyesi olduğu grup adlarını belirtmesi beklenen talebin adını belirtin. metadata_none: Bu bilgiye sahip değilim metadata_url: Bir keşif uç noktası URL'im var - oidc_information: These values are needed to configure the OpenID Connect provider. + oidc_information: Bu değerler OpenID Connect sağlayıcısını yapılandırmak için gereklidir. client_id: OpenID Connect sağlayıcısı tarafından size verilen istemci URL'si client_secret: OpenID Connect sağlayıcısı tarafından size verilen gizlilik URL'si limit_self_registration: Etkinleştirilirse, kullanıcılar yalnızca sağlayıcı tarafındaki yapılandırma izin veriyorsa bu sağlayıcıyı kullanarak kaydolabilir. @@ -96,20 +96,20 @@ tr: mapping_login: > Oturum açma özniteliği için kullanılmak üzere userinfo yanıtında özel bir eşleme sağlayın. mapping_email: > - Provide a custom mapping in the userinfo response to be used for the email attribute. + E-posta özniteliği için kullanılmak üzere userinfo yanıtında özel bir eşleme sağlayın. mapping_first_name: > - Provide a custom mapping in the userinfo response to be used for the first name. + İlk ad için kullanılmak üzere userinfo yanıtında özel bir eşleme sağlayın. mapping_last_name: > - Provide a custom mapping in the userinfo response to be used for the last name. + Soyadı için kullanılmak üzere userinfo yanıtında özel bir eşleme sağlayın. mapping_admin: > - Provide a custom mapping in the userinfo response to be used for the admin status. It expects a boolean attribute to be returned. + Yönetici durumu için kullanılmak üzere userinfo yanıtında özel bir eşleme sağlayın. Bir boolean özniteliğinin döndürülmesini bekler. settings: metadata_none: Bu bilgiye sahip değilim metadata_url: Bir keşif uç noktası URL'im var endpoint_url: Uç nokta adresi providers: label_providers: "Sağlayıcılar" - seeded_from_env: "This provider was seeded from the environment configuration. It cannot be edited." + seeded_from_env: "Bu sağlayıcı ön tanımlı olarak eklenmiştir. Düzenlenemez." google: name: Google microsoft_entra: @@ -117,14 +117,14 @@ tr: custom: name: Özel upsell: - title: "Single Sign-On (SSO) with OpenID connect" - description: Connect OpenProject to an OpenID connect identity provider + title: "OpenID connect ile Çoklu Oturum Açma (SSO)" + description: OpenProject'i bir SAML kimlik sağlayıcısına bağlama label_add_new: Yeni bir OpenID sağlayıcı ekle label_edit: OpenID sağlayıcıyı düzenle %{name} - label_empty_title: No OpenID providers configured yet. - label_empty_description: Add a provider to see them here. - label_group_mapping: Group mapping - label_metadata: OpenID Connect Discovery Endpoint + label_empty_title: Henüz yapılandırılmış OpenID sağlayıcısı yok. + label_empty_description: Onları burada görmek için bir sağlayıcı ekleyin. + label_group_mapping: Grup eşleme + label_metadata: OpenID Connect Keşif Uç Noktası label_automatic_configuration: Otomatik yapılandırma label_optional_configuration: İsteğe bağlı yapılandırma label_advanced_configuration: Gelişmiş yapılandırma @@ -132,24 +132,24 @@ tr: label_client_details: İstemci detayları label_attribute_mapping: Nitelik ilişkilendirme notice_created: Yeni bir OpenID sağlayıcısı başarıyla oluşturuldu. - client_details_description: Configuration details of OpenProject as an OIDC client + client_details_description: OpenProject'in bir OIDC istemcisi olarak yapılandırma ayrıntıları no_results_table: Henüz bir sağlayıcı tanımlanmadı. plural: OpenID sağlayıcıları singular: OpenID sağlayıcı section_texts: - group_mapping: Map group names provided by the identity provider to groups in OpenProject. - metadata: Pre-fill configuration using an OpenID Connect discovery endpoint URL - metadata_form_banner: Editing the discovery endpoint may override existing pre-filled metadata values. - metadata_form_title: OpenID Connect Discovery endpoint - metadata_form_description: If your identity provider has a discovery endpoint URL. Use it below to pre-fill configuration. - configuration_metadata: The information has been pre-filled using the supplied discovery endpoint. In most cases, they do not require editing. - configuration: Configuration details of the OpenID Connect provider + group_mapping: Kimlik sağlayıcı tarafından sağlanan grup adlarını OpenProject'teki gruplarla eşleştirin. + metadata: OpenID Connect keşif uç noktası URL'si kullanarak yapılandırmayı önceden doldurma + metadata_form_banner: Keşif uç noktasının düzenlenmesi, önceden doldurulmuş mevcut meta veri değerlerini geçersiz kılabilir. + metadata_form_title: OpenID Connect Keşif uç noktası + metadata_form_description: Kimlik sağlayıcınızın bir keşif uç noktası URL'si varsa. Yapılandırmayı önceden doldurmak için aşağıda kullanın. + configuration_metadata: Bu bilgiler, girilen meta verilerden otomatik olarak alınarak doldurulmuştur ve genellikle manuel düzenleme gerektirmez. + configuration: OpenID Connect sağlayıcısının yapılandırma ayrıntıları display_name: Görünen isim kullanıcılara görünür. - attribute_mapping: Configure the mapping of attributes between OpenProject and the OpenID Connect provider. - claims: Request additional claims for the ID token or userinfo response. + attribute_mapping: OpenProject ve OpenID Connect sağlayıcısı arasındaki özniteliklerin eşlemesini yapılandırın. + claims: ID token veya userinfo yanıtı için ek talepler isteyin. side_panel: information_component: - backchannel_logout_url: Backchannel logout URL + backchannel_logout_url: Arka kanal oturum kapatma URL'si setting_instructions: limit_self_registration: > Etkinleştirilirse, kullanıcılar bu sağlayıcıyı kullanarak yalnızca kendi kendine kayıt ayarı buna izin veriyorsa kaydolabilir. diff --git a/modules/storages/config/locales/crowdin/ja.yml b/modules/storages/config/locales/crowdin/ja.yml index 842cf88dc3f..4708d197140 100644 --- a/modules/storages/config/locales/crowdin/ja.yml +++ b/modules/storages/config/locales/crowdin/ja.yml @@ -534,7 +534,7 @@ ja: name_placeholder: 例:シェアポイント show_attachments_toggle: description: このオプションを無効にすると、ワークパッケージのファイルタブの添付ファイルリストが非表示になります。ワークパッケージの説明に添付されたファイルは、内部添付ファイルストレージにアップロードされます。 - label: ワークパッケージのファイルタブに添付ファイルを表示 + label: ワークパッケージファイルタブに添付ファイルを表示 storage_audience: documentation_intro: アイデンティティプロバイダの以下のオプションと設定については、当社のドキュメントをお読みください。 idp: diff --git a/modules/storages/config/locales/crowdin/tr.yml b/modules/storages/config/locales/crowdin/tr.yml index c5b2c75ef4a..a8896efd556 100644 --- a/modules/storages/config/locales/crowdin/tr.yml +++ b/modules/storages/config/locales/crowdin/tr.yml @@ -160,7 +160,7 @@ tr: group_does_not_exist: "%{group} mevcut değil. Nextcloud yapılandırmanızı kontrol edin." insufficient_privileges: OpenProject does not have enough privileges to add %{user} to %{group}. Check you group settings in Nextcloud. not_allowed: Nextcloud isteği engeller. - not_found: OpenProject could not find the file on the Nextcloud Storage Provider. Please check if it wasn't deleted. + not_found: OpenProject Nextcloud Depolama Sağlayıcısında dosyayı bulamadı. Lütfen silinip silinmediğini kontrol edin. unauthorized: OpenProject Nextcloud ile senkronize edilemedi. Lütfen depolama alanınızı ve Nextcloud yapılandırmanızı kontrol edin. user_does_not_exist: "%{user} does not exist in Nextcloud." one_drive_sync_service: @@ -238,23 +238,23 @@ tr: error_invalid_provider_type: Lütfen geçerli bir depolama sağlayıcısı seçin. file_storage_view: access_management: - automatic_management: Enable automatically-managed access and folders - automatic_management_description: Each project will be able to decide, when a storage is added to it, whether they want the automatically-managed or the manual approach to folder and access management. - description: OpenProject can automatically create and manage project folders when a file storage is added to a project. This can result in a more organized folder structure and straightforward access management that guarantees access to all relevant users. - manual_management: Only allow manually-managed access and folders - manual_management_description: Projects using this storage will not be offered the option of the automatically-managed approach. Folders and access must be managed manually. + automatic_management: Otomatik yönetilen giriş ve klasörleri aç. + automatic_management_description: Her proje, kendisine bir depolama alanı eklendiğinde, klasör ve erişim yönetimi için otomatik olarak yönetilen yaklaşımı mı yoksa manuel yaklaşımı mı kullanacağına karar verebilecektir. + description: OpenProject, bir projeye dosya depolama alanı eklendiğinde proje klasörlerini otomatik olarak oluşturup yönetebilir. Bu sayede daha düzenli bir klasör yapısı elde edilir ve ilgili tüm kullanıcılar için erişimin garanti altına alındığı, daha sade bir erişim yönetimi sağlanır. + manual_management: Yalnızca manuel olarak yönetilen erişim ve klasörlere izin verin + manual_management_description: Bu depolama alanını kullanan projelere otomatik olarak yönetilen yaklaşım seçeneği sunulmaz. Klasörler ve erişim izinleri manuel olarak yönetilmelidir. setup_incomplete: Select the type of management of user access and folder creation. - subtitle: Folder and access management + subtitle: Klasör ve erişim yönetimi access_management_section: Erişim ve proje klasörleri automatically_managed_folders: Otomatik olarak yönetilen klasörler general_information: Genel bilgi oauth_configuration: OAuth yapılandırması one_drive: access_management: - automatic_management: Enable automatically-managed access and folders - automatic_management_description: Each project using this file storage will necessarily have to use the automatically-managed approach to folder and access management. They cannot do it manually. - manual_management: Only allow manually-managed access and folders - manual_management_description: Projects using this storage will not be offered the option of the automatically-managed approach. Folders and access must be managed manually. + automatic_management: Otomatik olarak yönetilen erişimi ve klasörleri etkinleştirin + automatic_management_description: Bu dosya depolama alanını kullanan her proje, klasör ve erişim yönetimi için zorunlu olarak otomatik olarak yönetilen yaklaşımı kullanmak zorundadır. Manuel yönetim mümkün değildir. + manual_management: Yalnızca manuel olarak yönetilen erişim ve klasörlere izin verin + manual_management_description: Bu depolama alanını kullanan projelere otomatik olarak yönetilen yaklaşım seçeneği sunulmaz. Klasörler ve erişim izinleri manuel olarak yönetilmelidir. one_drive_oauth: Azure OAuth openproject_oauth: OpenProject OAuth project_folders: Proje klasörleri @@ -309,7 +309,7 @@ tr: failures: one: "%{count} kontrol başarısız" other: "%{count} kontrol başarısız" - success: All checks passed + success: Tüm kontroller geçti warnings: one: "%{count} kontrol bir uyarı döndürdü" other: "%{count} kontrol bir uyarı döndürdü" @@ -334,16 +334,16 @@ tr: od_oauth_request_unauthorized: The current user isn't authorized to access the remote file storage. Please check the server logs for further information. od_oauth_token_missing: OpenProject cannot test the user level communication with OneDrive as the user did not yet link their Microsoft account. od_tenant_id_wrong: The configured directory (tenant) id is invalid. Please check the configuration. - od_test_folder_exists: The folder %{folder_name} needed for testing already exists. Please delete it and try again. + od_test_folder_exists: Test amacıyla gereken %{folder_name} klasörü zaten var. Lütfen bu klasörü silin ve yeniden deneyin. od_unexpected_content: Sürücüde beklenmeyen içerik bulundu. offline_access_scope_missing: It is recommended to configure the OpenID Connect provider to request the offline_access scope. The integration may still work anyways, but make sure that refresh tokens do not expire. oidc_cant_refresh_token: There was an error while trying to check your access to the storage. Please check the server logs for further information. oidc_non_oidc_user: The current user, while provisioned, wasn't provisioned by an OpenID Connect (OIDC) Identity Provider. Please re-run the check with an OIDC provisioned user. oidc_non_provisioned_user: The current user isn't provided by an OpenID Connect Identity Provider. Please re-run the check with a provided user. oidc_provider_cant_exchange: The OpenID Connect provider does not seem to support token exchange, but token exchange was configured for the storage. - oidc_token_acquisition_failed: Your OpenID Connect setup doesn't provide the necessary audience, nor does it provide token exchange capabilities. Please check out our documentation for more information. - oidc_token_exchange_failed: There seems to be a problem with the Token Exchange setup on your OpenID Connect Provider. Please check its configuration and try again. - oidc_token_refresh_failed: There was an error while trying to check your access to the storage. Please check the server logs for further information. + oidc_token_acquisition_failed: OpenID Connect yapılandırmanız gerekli audience değerini ve token değişimi desteğini sağlamıyor. Ayrıntılar için dokümantasyona göz atın. + oidc_token_exchange_failed: OpenID Connect sağlayıcınızdaki Token Exchange yapılandırmasında bir sorun var gibi görünüyor. Lütfen yapılandırmayı kontrol edip tekrar deneyin. + oidc_token_refresh_failed: Depolama alanına erişiminiz kontrol edilirken bir hata oluştu. Daha fazla bilgi için lütfen sunucu günlüklerini kontrol edin. sp_client_cant_delete_folder: The client is having trouble deleting folders in SharePoint. Please check the setup documentation for your storage. sp_client_id_missing: The configured OAuth 2 client id is missing for SharePoint. Please check the configuration. sp_client_secret_missing: The configured OAuth 2 client secret is missing for SharePoint. Please check the configuration. @@ -463,7 +463,7 @@ tr: member_connection_status: connected: Bağlandı connected_no_permissions: Kullanıcı rolü depolama yetkisine sahip değil - not_connectable: Not connectable. The storage requires login through an SSO provider, but the user is not logging in through SSO. + not_connectable: Bağlanılamıyor. Depolama alanı SSO ile oturum açılmasını gerektiriyor; kullanıcı SSO üzerinden giriş yapmamış. not_connected: Bağlanmadı. Kullanıcı depolamaya %{link} adresini takip ederek bağlanmalı. not_connected_sso: Henüz bağlanmadı, SSO dosyalara baktıktan sonra bunları otomatik olarak bağlamalıdır. members_no_results: Gösterilecek üye yok. diff --git a/modules/team_planner/config/locales/crowdin/js-tr.yml b/modules/team_planner/config/locales/crowdin/js-tr.yml index 96b7b12478a..172dd83e59f 100644 --- a/modules/team_planner/config/locales/crowdin/js-tr.yml +++ b/modules/team_planner/config/locales/crowdin/js-tr.yml @@ -18,7 +18,7 @@ tr: work_week: 'Çalışma haftası' today: 'Bugün' drag_here_to_remove: 'Atanan kişiyi ve başlangıç ve bitiş tarihlerini kaldırmak için buraya sürükleyin.' - cannot_drag_here: 'Cannot move the work package due to permissions or editing restrictions.' + cannot_drag_here: 'İzinler veya düzenleme kısıtlamaları nedeniyle iş paketi taşınamıyor.' cannot_drag_to_non_working_day: 'Bu iş paketi, çalışma dışı bir günde başlayamaz veya bitemez.' quick_add: empty_state: 'İş paketlerini bulmak için arama alanını kullanın ve bunları birine atamak için planlayıcıya sürükleyin ve başlangıç ve bitiş tarihlerini tanımlayın.' diff --git a/modules/two_factor_authentication/config/locales/crowdin/tr.yml b/modules/two_factor_authentication/config/locales/crowdin/tr.yml index 7546fbe8a6a..91e9d810b76 100644 --- a/modules/two_factor_authentication/config/locales/crowdin/tr.yml +++ b/modules/two_factor_authentication/config/locales/crowdin/tr.yml @@ -123,7 +123,7 @@ tr: 2fa_from_webauthn: Lütfen WebAuthn cihazını %{device_name} sağlayın. USB tabanlı ise cihazınız taktığınızdan ve dokunduğunuzdan emin olun. Ardından oturum açma düğmesine tıklayın. webauthn: title: "WebAuthn" - description: Register a FIDO2 device (like YubiKey) or the secure enclave of your mobile device. + description: Bir FIDO2 cihazı (YubiKey gibi) ya da mobil cihazınızın güvenli donanım alanını (secure enclave) kaydedin. further_steps: Bir isim seçtikten sonra Devam düğmesine tıklayabilirsiniz. Tarayıcınız sizden WebAuthn cihazınızı göstermenizi isteyecektir. Bunu yaptığınızda, cihazın kaydını tamamlamış olursunuz. totp: title: "Uygulama tabanlı kimlik doğrulayıcı" diff --git a/modules/webhooks/config/locales/crowdin/tr.yml b/modules/webhooks/config/locales/crowdin/tr.yml index ef044afb5ce..52dafec7e34 100644 --- a/modules/webhooks/config/locales/crowdin/tr.yml +++ b/modules/webhooks/config/locales/crowdin/tr.yml @@ -28,9 +28,9 @@ tr: label_add_new: Yeni web kancası ekle label_edit: Web kancasını düzenle label_x_events: - one: "1 event" - other: "%{count} events" - zero: "No events" + one: "1 olay" + other: "%{count} olay" + zero: "Olay yok" events: created: "Oluşturuldu" updated: "Güncellenmiş" From 2cc106f560acaf26f78c2225f7cd47743e0192f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 05:33:14 +0000 Subject: [PATCH 234/293] Bump @html-eslint/eslint-plugin in /frontend in the html-eslint group Bumps the html-eslint group in /frontend with 1 update: [@html-eslint/eslint-plugin](https://github.com/yeonjuan/html-eslint). Updates `@html-eslint/eslint-plugin` from 0.54.0 to 0.54.2 - [Release notes](https://github.com/yeonjuan/html-eslint/releases) - [Commits](https://github.com/yeonjuan/html-eslint/compare/v0.54.0...v0.54.2) --- updated-dependencies: - dependency-name: "@html-eslint/eslint-plugin" dependency-version: 0.54.2 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: html-eslint ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 16 ++++++++-------- frontend/package.json | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2d7c355b75d..4de3f2bce46 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -136,7 +136,7 @@ "@angular-eslint/template-parser": "20.7.0", "@angular/language-service": "21.1.2", "@eslint/js": "^9.39.2", - "@html-eslint/eslint-plugin": "^0.54.0", + "@html-eslint/eslint-plugin": "^0.54.2", "@html-eslint/parser": "^0.54.0", "@jsdevtools/coverage-istanbul-loader": "3.0.5", "@stylistic/eslint-plugin": "^5.7.1", @@ -5188,9 +5188,9 @@ } }, "node_modules/@html-eslint/eslint-plugin": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@html-eslint/eslint-plugin/-/eslint-plugin-0.54.0.tgz", - "integrity": "sha512-9sXFPCiLL+PtppXUJoZZ9GjnaCck+oH+YwN4ZJZ4TC22vaMKVuuEuscxaGLwh6EdWFbKslrc9hlKiMAMJKoFdw==", + "version": "0.54.2", + "resolved": "https://registry.npmjs.org/@html-eslint/eslint-plugin/-/eslint-plugin-0.54.2.tgz", + "integrity": "sha512-C6jhJqVGTS9AW3Z84Ni/Cs6h3XcRHUXi1YkRaAYI08MeNj6ZWIXhwKBEJgEGK2YxzOcM1TpZEvHL4d5z7aC7Eg==", "dev": true, "dependencies": { "@eslint/plugin-kit": "^0.4.1", @@ -5203,7 +5203,7 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "peerDependencies": { - "eslint": "^8.0.0 || ^9.0.0" + "eslint": ">=8.0.0 || ^10.0.0-0" } }, "node_modules/@html-eslint/parser": { @@ -28866,9 +28866,9 @@ } }, "@html-eslint/eslint-plugin": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@html-eslint/eslint-plugin/-/eslint-plugin-0.54.0.tgz", - "integrity": "sha512-9sXFPCiLL+PtppXUJoZZ9GjnaCck+oH+YwN4ZJZ4TC22vaMKVuuEuscxaGLwh6EdWFbKslrc9hlKiMAMJKoFdw==", + "version": "0.54.2", + "resolved": "https://registry.npmjs.org/@html-eslint/eslint-plugin/-/eslint-plugin-0.54.2.tgz", + "integrity": "sha512-C6jhJqVGTS9AW3Z84Ni/Cs6h3XcRHUXi1YkRaAYI08MeNj6ZWIXhwKBEJgEGK2YxzOcM1TpZEvHL4d5z7aC7Eg==", "dev": true, "requires": { "@eslint/plugin-kit": "^0.4.1", diff --git a/frontend/package.json b/frontend/package.json index 50478a0686b..d05d72662c6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,7 +14,7 @@ "@angular-eslint/template-parser": "20.7.0", "@angular/language-service": "21.1.2", "@eslint/js": "^9.39.2", - "@html-eslint/eslint-plugin": "^0.54.0", + "@html-eslint/eslint-plugin": "^0.54.2", "@html-eslint/parser": "^0.54.0", "@jsdevtools/coverage-istanbul-loader": "3.0.5", "@stylistic/eslint-plugin": "^5.7.1", From 680d3293dd101be9b253b901b873d0e216e9b8b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Mon, 9 Feb 2026 14:39:30 +0100 Subject: [PATCH 235/293] Refactor installation uuid to use new persist_on_first_read --- app/models/setting.rb | 24 ----------- config/constants/settings/definition.rb | 6 ++- spec/helpers/security_badge_helper_spec.rb | 7 +--- spec/models/setting_spec.rb | 47 ---------------------- 4 files changed, 6 insertions(+), 78 deletions(-) diff --git a/app/models/setting.rb b/app/models/setting.rb index 84e9cfc7786..29f305290d5 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -80,8 +80,6 @@ class Setting < ApplicationRecord end def create_setting_accessors(name) - return if [:installation_uuid].include?(name.to_sym) - # Defines getter and setter for each setting # Then setting values can be read using: Setting.some_setting_name # or set using Setting.some_setting_name = "some value" @@ -223,28 +221,6 @@ class Setting < ApplicationRecord Settings::Definition[name].present? end - def self.installation_uuid - if settings_table_exists_yet? - # we avoid the default getters and setters since the cache messes things up - setting = find_or_initialize_by(name: "installation_uuid") - if setting.value.blank? - setting.value = generate_installation_uuid - setting.save! - end - setting.value - else - "unknown" - end - end - - def self.generate_installation_uuid - if Rails.env.test? - "test" - else - SecureRandom.uuid - end - end - %i[emails_header emails_footer].each do |mail| src = <<-END_SRC def self.localized_#{mail} diff --git a/config/constants/settings/definition.rb b/config/constants/settings/definition.rb index c9ec3467a1f..f5f0bb805f5 100644 --- a/config/constants/settings/definition.rb +++ b/config/constants/settings/definition.rb @@ -657,7 +657,11 @@ module Settings }, installation_uuid: { format: :string, - default: nil + default: -> { SecureRandom.uuid }, + persist_on_first_read: true, + default_by_env: { + test: "test_uuid" + } }, internal_password_confirmation: { description: "Require password confirmations for certain administrative actions", diff --git a/spec/helpers/security_badge_helper_spec.rb b/spec/helpers/security_badge_helper_spec.rb index b035dd0d3c7..fc49474251c 100644 --- a/spec/helpers/security_badge_helper_spec.rb +++ b/spec/helpers/security_badge_helper_spec.rb @@ -32,17 +32,12 @@ require "spec_helper" RSpec.describe SecurityBadgeHelper do describe "#security_badge_url" do - before do - # can't use with_settings since Setting.installation_uuid has a custom implementation - allow(Setting).to receive(:installation_uuid).and_return "abcd1234" - end - it "generates a URL with the release API path and the details of the installation" do uri = URI.parse(helper.security_badge_url) query = Rack::Utils.parse_nested_query(uri.query) expect(uri.host).to eq("releases.openproject.com") expect(query.keys).to contain_exactly("uuid", "type", "version", "db", "lang", "ee") - expect(query["uuid"]).to eq("abcd1234") + expect(query["uuid"]).to eq("test_uuid") expect(query["version"]).to eq(OpenProject::VERSION.to_semver) expect(query["type"]).to eq("manual") expect(query["ee"]).to eq("false") diff --git a/spec/models/setting_spec.rb b/spec/models/setting_spec.rb index 5bd7c2dd564..cdb2605877d 100644 --- a/spec/models/setting_spec.rb +++ b/spec/models/setting_spec.rb @@ -267,53 +267,6 @@ RSpec.describe Setting do end end - describe ".installation_uuid" do - after do - described_class.find_by(name: "installation_uuid")&.destroy - end - - it "returns unknown if the settings table isn't available yet" do - allow(described_class) - .to receive(:settings_table_exists_yet?) - .and_return(false) - expect(described_class.installation_uuid).to eq("unknown") - end - - context "with settings table ready" do - it "resets the value if blank" do - described_class.create!(name: "installation_uuid", value: "") - expect(described_class.installation_uuid).not_to be_blank - end - - it "returns the existing value if any" do - # can't use with_settings since described_class.installation_uuid has a custom implementation - allow(described_class).to receive(:installation_uuid).and_return "abcd1234" - - expect(described_class.installation_uuid).to eq("abcd1234") - end - - context "with no existing value" do - context "in test environment" do - before do - expect(Rails.env).to receive(:test?).and_return(true) - end - - it "returns 'test' as the UUID" do - expect(described_class.installation_uuid).to eq("test") - end - end - - it "returns a random UUID" do - expect(Rails.env).to receive(:test?).and_return(false) - installation_uuid = described_class.installation_uuid - expect(installation_uuid).not_to eq("test") - expect(installation_uuid.size).to eq(36) - expect(described_class.installation_uuid).to eq(installation_uuid) - end - end - end - end - # Check that when reading certain setting values that they get overwritten if needed. describe "filter saved settings" do it "returns the value for 'work_package_list_default_highlighting_mode' without changing it" do From 508c8bbad79125c1108c1f2e15bb31fa1fbe4817 Mon Sep 17 00:00:00 2001 From: Jan Sandbrink Date: Mon, 9 Feb 2026 16:42:49 +0100 Subject: [PATCH 236/293] Always respond in Bearer method for WWW-Authenticate header The intention of this change is to always respond in the metadata-rich version of the header that indicates things like the required scope and the URL of the resource_metadata endpoint, which was previously hidden and only visible if clients used a non-standard HTTP request header. semantically it's probably the preferable version of the header by now anyways, because: * all APIs accept some kind of Bearer token, not all of them accept Basic auth * Even API tokens can now be passed as Bearer tokens Practically the Basic auth header also caused unintended browser pop-ups when the frontend code didn't include the correct request header to avoid the Basic auth offer, this now can't happen anymore, since the Basic auth version of the header is only returned, if the client actively tried to authenticate through Basic auth. --- .../concerns/accounts/current_user.rb | 2 +- config/initializers/warden.rb | 22 ++++----- .../app/core/turbo/turbo-requests.service.ts | 4 +- .../http/openproject-header-interceptor.ts | 4 +- .../helpers/connection-template-fetcher.ts | 1 - .../controllers/async-dialog.controller.ts | 1 - .../documents/live-events.controller.ts | 1 - .../dynamic/forum-messages.controller.ts | 1 - ...equire-password-confirmation.controller.ts | 1 - .../documents/token-refresh.service.ts | 1 - frontend/src/turbo/turbo-event-listeners.ts | 1 - lib/api/root_api.rb | 3 +- lib_static/open_project/authentication.rb | 48 ++++--------------- .../authentication/failure_app.rb | 3 +- .../strategies/warden/fail_with_header.rb | 1 - spec/requests/api/v3/authentication_spec.rb | 39 +++------------ .../mcp/mcp_resources/current_user_spec.rb | 1 - .../mcp/mcp_resources/project_spec.rb | 1 - .../mcp/mcp_resources/status_list_spec.rb | 1 - .../requests/mcp/mcp_resources/status_spec.rb | 1 - .../mcp/mcp_resources/type_list_spec.rb | 1 - spec/requests/mcp/mcp_resources/type_spec.rb | 1 - spec/requests/mcp/mcp_resources/user_spec.rb | 1 - .../mcp/mcp_resources/version_spec.rb | 1 - .../mcp/mcp_resources/work_package_spec.rb | 1 - .../mcp/mcp_tools/current_user_spec.rb | 1 - .../mcp/mcp_tools/list_statuses_spec.rb | 1 - .../requests/mcp/mcp_tools/list_types_spec.rb | 1 - .../mcp/mcp_tools/search_projects_spec.rb | 1 - .../mcp/mcp_tools/search_users_spec.rb | 1 - .../mcp_tools/search_work_packages_spec.rb | 1 - .../mcp/resource_templates_list_spec.rb | 2 - spec/requests/mcp/resources_list_spec.rb | 2 - spec/requests/mcp/tools_list_spec.rb | 4 -- 34 files changed, 28 insertions(+), 128 deletions(-) diff --git a/app/controllers/concerns/accounts/current_user.rb b/app/controllers/concerns/accounts/current_user.rb index bc089498e8c..b26e8a7d09b 100644 --- a/app/controllers/concerns/accounts/current_user.rb +++ b/app/controllers/concerns/accounts/current_user.rb @@ -168,7 +168,7 @@ module Accounts::CurrentUser redirect_to main_app.signin_path(back_url: login_back_url) end - auth_header = OpenProject::Authentication::WWWAuthenticate.response_header(request_headers: request.headers) + auth_header = OpenProject::Authentication::WWWAuthenticate.response_header format.any(:xml, :js, :json, :turbo_stream) do head :unauthorized, diff --git a/config/initializers/warden.rb b/config/initializers/warden.rb index bb715711ba5..a19ec7d5f0a 100644 --- a/config/initializers/warden.rb +++ b/config/initializers/warden.rb @@ -30,20 +30,14 @@ namespace = OpenProject::Authentication::Strategies::Warden -strategies = [ - [:basic_auth_failure, namespace::BasicAuthFailure, "Basic"], - [:global_basic_auth, namespace::GlobalBasicAuth, "Basic"], - [:user_basic_auth, namespace::UserBasicAuth, "Basic"], - [:user_api_token, namespace::UserAPIToken, "Bearer"], - [:oauth, namespace::DoorkeeperOAuth, "Bearer"], - [:anonymous_fallback, namespace::AnonymousFallback, "Basic"], - [:jwt_oidc, namespace::JwtOidc, "Bearer"], - [:session, namespace::Session, "Session"] -] - -strategies.each do |name, clazz, auth_scheme| - OpenProject::Authentication.add_strategy(name, clazz, auth_scheme) -end +OpenProject::Authentication.add_strategy(:basic_auth_failure, namespace::BasicAuthFailure, "Basic") +OpenProject::Authentication.add_strategy(:global_basic_auth, namespace::GlobalBasicAuth, "Basic") +OpenProject::Authentication.add_strategy(:user_basic_auth, namespace::UserBasicAuth, "Basic") +OpenProject::Authentication.add_strategy(:user_api_token, namespace::UserAPIToken, "Bearer") +OpenProject::Authentication.add_strategy(:oauth, namespace::DoorkeeperOAuth, "Bearer") +OpenProject::Authentication.add_strategy(:anonymous_fallback, namespace::AnonymousFallback, "Basic") +OpenProject::Authentication.add_strategy(:jwt_oidc, namespace::JwtOidc, "Bearer") +OpenProject::Authentication.add_strategy(:session, namespace::Session, "Session") OpenProject::Authentication.update_strategies(OpenProject::Authentication::Scope::API_V3, { store: false }) do |_| %i[global_basic_auth diff --git a/frontend/src/app/core/turbo/turbo-requests.service.ts b/frontend/src/app/core/turbo/turbo-requests.service.ts index 71614a5a996..a1a40087ddc 100644 --- a/frontend/src/app/core/turbo/turbo-requests.service.ts +++ b/frontend/src/app/core/turbo/turbo-requests.service.ts @@ -32,9 +32,7 @@ export class TurboRequestsService { init.signal = controller.signal; } - const defaultHeaders:{'X-Authentication-Scheme':string, 'X-CSRF-Token'?:string} = { - 'X-Authentication-Scheme': 'Session', - }; + const defaultHeaders:{'X-CSRF-Token'?:string} = {}; if(init.method && !(init.method === 'GET' || init.method === 'HEAD')) { defaultHeaders['X-CSRF-Token'] = getMetaContent('csrf-token'); } diff --git a/frontend/src/app/features/hal/http/openproject-header-interceptor.ts b/frontend/src/app/features/hal/http/openproject-header-interceptor.ts index 259ca7db3d6..5cc066766eb 100644 --- a/frontend/src/app/features/hal/http/openproject-header-interceptor.ts +++ b/frontend/src/app/features/hal/http/openproject-header-interceptor.ts @@ -32,9 +32,7 @@ export class OpenProjectHeaderInterceptor implements HttpInterceptor { private handleAuthenticatedRequest(req:HttpRequest, next:HttpHandler):Observable> { const csrfToken = getMetaContent('csrf-token'); - let newHeaders = req.headers - .set('X-Authentication-Scheme', 'Session') - .set('X-Requested-With', 'XMLHttpRequest'); + let newHeaders = req.headers.set('X-Requested-With', 'XMLHttpRequest'); if (csrfToken) { newHeaders = newHeaders.set('X-CSRF-TOKEN', csrfToken); diff --git a/frontend/src/react/helpers/connection-template-fetcher.ts b/frontend/src/react/helpers/connection-template-fetcher.ts index 21721ca8dcd..090bc6df265 100644 --- a/frontend/src/react/helpers/connection-template-fetcher.ts +++ b/frontend/src/react/helpers/connection-template-fetcher.ts @@ -80,7 +80,6 @@ export async function fetchConnectionTemplate( method: 'GET', headers: { Accept: 'text/vnd.turbo-stream.html', - 'X-Authentication-Scheme': 'Session', }, }); diff --git a/frontend/src/stimulus/controllers/async-dialog.controller.ts b/frontend/src/stimulus/controllers/async-dialog.controller.ts index 94d912b513f..6279b34b814 100644 --- a/frontend/src/stimulus/controllers/async-dialog.controller.ts +++ b/frontend/src/stimulus/controllers/async-dialog.controller.ts @@ -61,7 +61,6 @@ export default class AsyncDialogController extends ApplicationController { method: this.method, headers: { Accept: 'text/vnd.turbo-stream.html', - 'X-Authentication-Scheme': 'Session', }, }).then((response) => { const contentType = response.headers.get('Content-Type') ?? ''; diff --git a/frontend/src/stimulus/controllers/dynamic/documents/live-events.controller.ts b/frontend/src/stimulus/controllers/dynamic/documents/live-events.controller.ts index 25987fab7a4..10ba172e331 100644 --- a/frontend/src/stimulus/controllers/dynamic/documents/live-events.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/documents/live-events.controller.ts @@ -119,7 +119,6 @@ export default class extends ApplicationController { method: 'GET', headers: { Accept: 'text/vnd.turbo-stream.html', - 'X-Authentication-Scheme': 'Session', }, }) .then((response:Response) => { diff --git a/frontend/src/stimulus/controllers/dynamic/forum-messages.controller.ts b/frontend/src/stimulus/controllers/dynamic/forum-messages.controller.ts index dba8d1a9d43..0f60c92409a 100644 --- a/frontend/src/stimulus/controllers/dynamic/forum-messages.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/forum-messages.controller.ts @@ -53,7 +53,6 @@ export default class ForumMessagesController extends Controller { void fetch(href, { headers: { Accept: 'application/json', - 'X-Authentication-Scheme': 'Session', }, }) .then((response) => response.json()) diff --git a/frontend/src/stimulus/controllers/require-password-confirmation.controller.ts b/frontend/src/stimulus/controllers/require-password-confirmation.controller.ts index 29d57aaf461..847536e2a27 100644 --- a/frontend/src/stimulus/controllers/require-password-confirmation.controller.ts +++ b/frontend/src/stimulus/controllers/require-password-confirmation.controller.ts @@ -91,7 +91,6 @@ export default class RequirePasswordConfirmationController extends ApplicationCo method: 'GET', headers: { Accept: 'text/vnd.turbo-stream.html', - 'X-Authentication-Scheme': 'Session', }, }).then((response) => { const contentType = response.headers.get('Content-Type') ?? ''; diff --git a/frontend/src/stimulus/services/documents/token-refresh.service.ts b/frontend/src/stimulus/services/documents/token-refresh.service.ts index 7b49e919593..b77082ee63c 100644 --- a/frontend/src/stimulus/services/documents/token-refresh.service.ts +++ b/frontend/src/stimulus/services/documents/token-refresh.service.ts @@ -120,7 +120,6 @@ export class TokenRefreshService { headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': getMetaContent('csrf-token'), - 'X-Authentication-Scheme': 'Session', }, credentials: 'same-origin', }); diff --git a/frontend/src/turbo/turbo-event-listeners.ts b/frontend/src/turbo/turbo-event-listeners.ts index df6659ffcc2..37e7ef0f0d4 100644 --- a/frontend/src/turbo/turbo-event-listeners.ts +++ b/frontend/src/turbo/turbo-event-listeners.ts @@ -31,7 +31,6 @@ export function addTurboEventListeners() { const headers = event.detail.fetchOptions.headers as Record; headers['Turbo-Referrer'] = window.location.href; headers['X-Turbo-Nonce'] = document.getElementsByName('csp-nonce')[0]?.getAttribute('content') || ''; - headers['X-Authentication-Scheme'] = 'Session'; }); // Turbo adds nonces to all scripts, even though we want to explicitly pass nonces diff --git a/lib/api/root_api.rb b/lib/api/root_api.rb index f01862c21d9..abf986df567 100644 --- a/lib/api/root_api.rb +++ b/lib/api/root_api.rb @@ -279,8 +279,7 @@ module API def self.auth_headers lambda do - header = OpenProject::Authentication::WWWAuthenticate - .response_header(scope: authentication_scope, request_headers: env) + header = OpenProject::Authentication::WWWAuthenticate.response_header(scope: authentication_scope) { "WWW-Authenticate" => header } end diff --git a/lib_static/open_project/authentication.rb b/lib_static/open_project/authentication.rb index 0f8b6ca353a..3a128db2434 100644 --- a/lib_static/open_project/authentication.rb +++ b/lib_static/open_project/authentication.rb @@ -240,20 +240,6 @@ module OpenProject module WWWAuthenticate module_function - def pick_auth_scheme(supported_schemes, default_scheme, request_headers = {}) - req_scheme = request_headers["HTTP_X_AUTHENTICATION_SCHEME"] - - if supported_schemes.include? req_scheme - req_scheme - else - default_scheme - end - end - - def default_auth_scheme - "Basic" - end - def default_realm "OpenProject API" end @@ -262,36 +248,18 @@ module OpenProject Manager.scope_config(scope).realm || default_realm end - def response_header( - default_auth_scheme: self.default_auth_scheme, - scope: nil, - request_headers: {}, - error: nil, - error_description: nil - ) - scheme = pick_auth_scheme(auth_schemes(scope), - default_auth_scheme, - request_headers) + def response_header(scope: nil, error: nil, error_description: nil) + header = %{Bearer realm="#{scope_realm(scope)}", resource_metadata="#{resource_metadata}"} + header << %{, scope="#{escape_string scope}"} if scope - header = %{#{scheme} realm="#{scope_realm(scope)}"} - if scheme == "Bearer" - header << %{, resource_metadata="#{resource_metadata}"} - header << %{, scope="#{escape_string scope}"} if scope + if error + header << %{, error="#{escape_string error}"} + header << %{, error_description="#{escape_string error_description}"} if error_description end - header << %{, error="#{escape_string error}"} if error - header << %{, error_description="#{escape_string error_description}"} if error && error_description header end - def auth_schemes(scope) - strategies = Array(Manager.scope_config(scope).strategies) - - Manager.auth_schemes - .select { |_, info| scope.nil? or info.strategies.intersect?(strategies) } - .keys - end - def escape_string(string) string.to_s.dump[1..-2] end @@ -301,13 +269,15 @@ module OpenProject end end + # Prepended to the warden basic auth strategy, so when a client already tries to use Basic auth (but fails), they + # will receive a Basic WWW-Authenticate header. module AuthHeaders include WWWAuthenticate # #scope available from Warden::Strategies::BasicAuth def auth_scheme - pick_auth_scheme auth_schemes(scope), default_auth_scheme, env + "Basic" end def realm diff --git a/lib_static/open_project/authentication/failure_app.rb b/lib_static/open_project/authentication/failure_app.rb index e5dccf4d13e..e5b4f7c6103 100644 --- a/lib_static/open_project/authentication/failure_app.rb +++ b/lib_static/open_project/authentication/failure_app.rb @@ -75,8 +75,7 @@ module OpenProject end def unauthorized_header(env) - header = OpenProject::Authentication::WWWAuthenticate - .response_header(scope: scope(env), request_headers: env) + header = OpenProject::Authentication::WWWAuthenticate.response_header(scope: scope(env)) { "WWW-Authenticate" => header } end diff --git a/lib_static/open_project/authentication/strategies/warden/fail_with_header.rb b/lib_static/open_project/authentication/strategies/warden/fail_with_header.rb index ae28af812f5..d8758edf24a 100644 --- a/lib_static/open_project/authentication/strategies/warden/fail_with_header.rb +++ b/lib_static/open_project/authentication/strategies/warden/fail_with_header.rb @@ -36,7 +36,6 @@ module OpenProject def fail_with_header!(error:, error_description: nil) headers( "WWW-Authenticate" => OpenProject::Authentication::WWWAuthenticate.response_header( - default_auth_scheme: "Bearer", scope:, error:, error_description: diff --git a/spec/requests/api/v3/authentication_spec.rb b/spec/requests/api/v3/authentication_spec.rb index 27229bcc0fb..36ab7eb7830 100644 --- a/spec/requests/api/v3/authentication_spec.rb +++ b/spec/requests/api/v3/authentication_spec.rb @@ -187,6 +187,9 @@ RSpec.describe "API V3 Authentication" do let(:token) { create(:oauth_access_token, resource_owner: nil, application:) } let(:application) { create(:oauth_application) } let(:oauth_access_token) { token.plaintext_token } + let(:expected_www_auth_header) do + %{Bearer realm="OpenProject API", #{resource_metadata}, scope="api_v3"} + end # Note: This is just caused by DoorkeeperOauth rejecting to handle this case and auth falling through to basic auth # more specific examples can be found at spec/requests/oauth/client_credentials_flow_spec.rb @@ -194,10 +197,7 @@ RSpec.describe "API V3 Authentication" do it "returns unauthorized" do expect(last_response).to have_http_status :unauthorized - - # Note: This is just caused by DoorkeeperOauth rejecting to handle this case and auth falling through to basic auth - # more specific examples can be found at spec/requests/oauth/client_credentials_flow_spec.rb - expect(last_response.header["WWW-Authenticate"]).to eq('Basic realm="OpenProject API"') + expect(last_response.header["WWW-Authenticate"]).to eq(expected_www_auth_header) expect(JSON.parse(last_response.body)).to eq(error_response_body) end @@ -332,7 +332,7 @@ RSpec.describe "API V3 Authentication" do end it "returns the WWW-Authenticate header" do - expect(last_response.header["WWW-Authenticate"]).to include 'Basic realm="OpenProject API"' + expect(last_response.header["WWW-Authenticate"]).to include 'Bearer realm="OpenProject API"' end end @@ -383,34 +383,7 @@ RSpec.describe "API V3 Authentication" do it "returns the WWW-Authenticate header" do expect(last_response.header["WWW-Authenticate"]) - .to include 'Basic realm="OpenProject API"' - end - end - - context 'with invalid credentials an X-Authentication-Scheme "Session"' do - let(:expected_message) { "You did not provide the correct credentials." } - - before do - set_basic_auth_header(username, password.reverse) - header "X-Authentication-Scheme", "Session" - get resource - end - - it "returns 401 unauthorized" do - expect(last_response).to have_http_status :unauthorized - end - - it "returns the correct JSON response" do - expect(JSON.parse(last_response.body)).to eq error_response_body - end - - it "returns the correct content type header" do - expect(last_response.headers["Content-Type"]).to eq "application/hal+json; charset=utf-8" - end - - it "returns the WWW-Authenticate header" do - expect(last_response.header["WWW-Authenticate"]) - .to include 'Session realm="OpenProject API"' + .to include 'Bearer realm="OpenProject API"' end end diff --git a/spec/requests/mcp/mcp_resources/current_user_spec.rb b/spec/requests/mcp/mcp_resources/current_user_spec.rb index a6cac80f3d9..2aad4184b76 100644 --- a/spec/requests/mcp/mcp_resources/current_user_spec.rb +++ b/spec/requests/mcp/mcp_resources/current_user_spec.rb @@ -33,7 +33,6 @@ require "spec_helper" RSpec.describe McpResources::CurrentUser, with_flag: { mcp_server: true } do subject do header "Authorization", "Bearer #{access_token.plaintext_token}" - header "X-Authentication-Scheme", "Bearer" header "Content-Type", "application/json" post "/mcp", request_body.to_json end diff --git a/spec/requests/mcp/mcp_resources/project_spec.rb b/spec/requests/mcp/mcp_resources/project_spec.rb index 314879c9b62..c437c2b92c3 100644 --- a/spec/requests/mcp/mcp_resources/project_spec.rb +++ b/spec/requests/mcp/mcp_resources/project_spec.rb @@ -33,7 +33,6 @@ require "spec_helper" RSpec.describe McpResources::Project, with_flag: { mcp_server: true } do subject do header "Authorization", "Bearer #{access_token.plaintext_token}" - header "X-Authentication-Scheme", "Bearer" header "Content-Type", "application/json" post "/mcp", request_body.to_json end diff --git a/spec/requests/mcp/mcp_resources/status_list_spec.rb b/spec/requests/mcp/mcp_resources/status_list_spec.rb index 94ea2697b14..8588ad7bd99 100644 --- a/spec/requests/mcp/mcp_resources/status_list_spec.rb +++ b/spec/requests/mcp/mcp_resources/status_list_spec.rb @@ -33,7 +33,6 @@ require "spec_helper" RSpec.describe McpResources::StatusList, with_flag: { mcp_server: true } do subject do header "Authorization", "Bearer #{access_token.plaintext_token}" - header "X-Authentication-Scheme", "Bearer" header "Content-Type", "application/json" post "/mcp", request_body.to_json end diff --git a/spec/requests/mcp/mcp_resources/status_spec.rb b/spec/requests/mcp/mcp_resources/status_spec.rb index 22661eb6f8f..2b660c8df09 100644 --- a/spec/requests/mcp/mcp_resources/status_spec.rb +++ b/spec/requests/mcp/mcp_resources/status_spec.rb @@ -33,7 +33,6 @@ require "spec_helper" RSpec.describe McpResources::Status, with_flag: { mcp_server: true } do subject do header "Authorization", "Bearer #{access_token.plaintext_token}" - header "X-Authentication-Scheme", "Bearer" header "Content-Type", "application/json" post "/mcp", request_body.to_json end diff --git a/spec/requests/mcp/mcp_resources/type_list_spec.rb b/spec/requests/mcp/mcp_resources/type_list_spec.rb index 64daa452140..6fe38b4d107 100644 --- a/spec/requests/mcp/mcp_resources/type_list_spec.rb +++ b/spec/requests/mcp/mcp_resources/type_list_spec.rb @@ -33,7 +33,6 @@ require "spec_helper" RSpec.describe McpResources::TypeList, with_flag: { mcp_server: true } do subject do header "Authorization", "Bearer #{access_token.plaintext_token}" - header "X-Authentication-Scheme", "Bearer" header "Content-Type", "application/json" post "/mcp", request_body.to_json end diff --git a/spec/requests/mcp/mcp_resources/type_spec.rb b/spec/requests/mcp/mcp_resources/type_spec.rb index c99038c3cdd..c9677559946 100644 --- a/spec/requests/mcp/mcp_resources/type_spec.rb +++ b/spec/requests/mcp/mcp_resources/type_spec.rb @@ -33,7 +33,6 @@ require "spec_helper" RSpec.describe McpResources::Type, with_flag: { mcp_server: true } do subject do header "Authorization", "Bearer #{access_token.plaintext_token}" - header "X-Authentication-Scheme", "Bearer" header "Content-Type", "application/json" post "/mcp", request_body.to_json end diff --git a/spec/requests/mcp/mcp_resources/user_spec.rb b/spec/requests/mcp/mcp_resources/user_spec.rb index 1a8ff3afd1c..f3099390d71 100644 --- a/spec/requests/mcp/mcp_resources/user_spec.rb +++ b/spec/requests/mcp/mcp_resources/user_spec.rb @@ -33,7 +33,6 @@ require "spec_helper" RSpec.describe McpResources::User, with_flag: { mcp_server: true } do subject do header "Authorization", "Bearer #{access_token.plaintext_token}" - header "X-Authentication-Scheme", "Bearer" header "Content-Type", "application/json" post "/mcp", request_body.to_json end diff --git a/spec/requests/mcp/mcp_resources/version_spec.rb b/spec/requests/mcp/mcp_resources/version_spec.rb index ad75e06c0b6..14ffd0dd655 100644 --- a/spec/requests/mcp/mcp_resources/version_spec.rb +++ b/spec/requests/mcp/mcp_resources/version_spec.rb @@ -33,7 +33,6 @@ require "spec_helper" RSpec.describe McpResources::Version, with_flag: { mcp_server: true } do subject do header "Authorization", "Bearer #{access_token.plaintext_token}" - header "X-Authentication-Scheme", "Bearer" header "Content-Type", "application/json" post "/mcp", request_body.to_json end diff --git a/spec/requests/mcp/mcp_resources/work_package_spec.rb b/spec/requests/mcp/mcp_resources/work_package_spec.rb index d3e82384fea..2ff0877d6d9 100644 --- a/spec/requests/mcp/mcp_resources/work_package_spec.rb +++ b/spec/requests/mcp/mcp_resources/work_package_spec.rb @@ -33,7 +33,6 @@ require "spec_helper" RSpec.describe McpResources::WorkPackage, with_flag: { mcp_server: true } do subject do header "Authorization", "Bearer #{access_token.plaintext_token}" - header "X-Authentication-Scheme", "Bearer" header "Content-Type", "application/json" post "/mcp", request_body.to_json end diff --git a/spec/requests/mcp/mcp_tools/current_user_spec.rb b/spec/requests/mcp/mcp_tools/current_user_spec.rb index 646ad5cdce7..ef5892b0d29 100644 --- a/spec/requests/mcp/mcp_tools/current_user_spec.rb +++ b/spec/requests/mcp/mcp_tools/current_user_spec.rb @@ -33,7 +33,6 @@ require "spec_helper" RSpec.describe McpTools::CurrentUser, with_flag: { mcp_server: true } do subject do header "Authorization", "Bearer #{access_token.plaintext_token}" - header "X-Authentication-Scheme", "Bearer" header "Content-Type", "application/json" post "/mcp", request_body.to_json end diff --git a/spec/requests/mcp/mcp_tools/list_statuses_spec.rb b/spec/requests/mcp/mcp_tools/list_statuses_spec.rb index 182fb9fe7ff..956707032b6 100644 --- a/spec/requests/mcp/mcp_tools/list_statuses_spec.rb +++ b/spec/requests/mcp/mcp_tools/list_statuses_spec.rb @@ -33,7 +33,6 @@ require "spec_helper" RSpec.describe McpTools::ListStatuses, with_flag: { mcp_server: true } do subject do header "Authorization", "Bearer #{access_token.plaintext_token}" - header "X-Authentication-Scheme", "Bearer" header "Content-Type", "application/json" post "/mcp", request_body.to_json end diff --git a/spec/requests/mcp/mcp_tools/list_types_spec.rb b/spec/requests/mcp/mcp_tools/list_types_spec.rb index d7fea3943b8..c5533f63268 100644 --- a/spec/requests/mcp/mcp_tools/list_types_spec.rb +++ b/spec/requests/mcp/mcp_tools/list_types_spec.rb @@ -33,7 +33,6 @@ require "spec_helper" RSpec.describe McpTools::ListTypes, with_flag: { mcp_server: true } do subject do header "Authorization", "Bearer #{access_token.plaintext_token}" - header "X-Authentication-Scheme", "Bearer" header "Content-Type", "application/json" post "/mcp", request_body.to_json end diff --git a/spec/requests/mcp/mcp_tools/search_projects_spec.rb b/spec/requests/mcp/mcp_tools/search_projects_spec.rb index d04186ec758..11ac795c07e 100644 --- a/spec/requests/mcp/mcp_tools/search_projects_spec.rb +++ b/spec/requests/mcp/mcp_tools/search_projects_spec.rb @@ -33,7 +33,6 @@ require "spec_helper" RSpec.describe McpTools::SearchProjects, with_flag: { mcp_server: true } do subject do header "Authorization", "Bearer #{access_token.plaintext_token}" - header "X-Authentication-Scheme", "Bearer" header "Content-Type", "application/json" post "/mcp", request_body.to_json end diff --git a/spec/requests/mcp/mcp_tools/search_users_spec.rb b/spec/requests/mcp/mcp_tools/search_users_spec.rb index 9b878ed2021..8de17a8cc29 100644 --- a/spec/requests/mcp/mcp_tools/search_users_spec.rb +++ b/spec/requests/mcp/mcp_tools/search_users_spec.rb @@ -33,7 +33,6 @@ require "spec_helper" RSpec.describe McpTools::SearchUsers, with_flag: { mcp_server: true } do subject do header "Authorization", "Bearer #{access_token.plaintext_token}" - header "X-Authentication-Scheme", "Bearer" header "Content-Type", "application/json" post "/mcp", request_body.to_json end diff --git a/spec/requests/mcp/mcp_tools/search_work_packages_spec.rb b/spec/requests/mcp/mcp_tools/search_work_packages_spec.rb index f5f0ab60e55..ce31b2e6f7f 100644 --- a/spec/requests/mcp/mcp_tools/search_work_packages_spec.rb +++ b/spec/requests/mcp/mcp_tools/search_work_packages_spec.rb @@ -33,7 +33,6 @@ require "spec_helper" RSpec.describe McpTools::SearchWorkPackages, with_flag: { mcp_server: true } do subject do header "Authorization", "Bearer #{access_token.plaintext_token}" - header "X-Authentication-Scheme", "Bearer" header "Content-Type", "application/json" post "/mcp", request_body.to_json end diff --git a/spec/requests/mcp/resource_templates_list_spec.rb b/spec/requests/mcp/resource_templates_list_spec.rb index 4b1621faddf..202754c1372 100644 --- a/spec/requests/mcp/resource_templates_list_spec.rb +++ b/spec/requests/mcp/resource_templates_list_spec.rb @@ -33,7 +33,6 @@ require "spec_helper" RSpec.describe "MCP resources/templates/list", with_flag: { mcp_server: true } do subject do header "Authorization", "Bearer #{access_token.plaintext_token}" - header "X-Authentication-Scheme", "Bearer" header "Content-Type", "application/json" post "/mcp", request_body.to_json end @@ -87,7 +86,6 @@ RSpec.describe "MCP resources/templates/list", with_flag: { mcp_server: true } d context "when not passing a Bearer token" do subject do - header "X-Authentication-Scheme", "Bearer" header "Content-Type", "application/json" post "/mcp", request_body.to_json end diff --git a/spec/requests/mcp/resources_list_spec.rb b/spec/requests/mcp/resources_list_spec.rb index 757cc1d6f5a..e517ec41672 100644 --- a/spec/requests/mcp/resources_list_spec.rb +++ b/spec/requests/mcp/resources_list_spec.rb @@ -33,7 +33,6 @@ require "spec_helper" RSpec.describe "MCP resources/list", with_flag: { mcp_server: true } do subject do header "Authorization", "Bearer #{access_token.plaintext_token}" - header "X-Authentication-Scheme", "Bearer" header "Content-Type", "application/json" post "/mcp", request_body.to_json end @@ -87,7 +86,6 @@ RSpec.describe "MCP resources/list", with_flag: { mcp_server: true } do context "when not passing a Bearer token" do subject do - header "X-Authentication-Scheme", "Bearer" header "Content-Type", "application/json" post "/mcp", request_body.to_json end diff --git a/spec/requests/mcp/tools_list_spec.rb b/spec/requests/mcp/tools_list_spec.rb index d9afec868d9..0e4cc6d7472 100644 --- a/spec/requests/mcp/tools_list_spec.rb +++ b/spec/requests/mcp/tools_list_spec.rb @@ -33,7 +33,6 @@ require "spec_helper" RSpec.describe "MCP tools/list", with_flag: { mcp_server: true } do subject do header "Authorization", "Bearer #{access_token.plaintext_token}" - header "X-Authentication-Scheme", "Bearer" header "Content-Type", "application/json" post "/mcp", request_body.to_json end @@ -71,9 +70,6 @@ RSpec.describe "MCP tools/list", with_flag: { mcp_server: true } do context "when not passing a token" do subject do - # TODO: It's actually a hack that we expect clients to provide this header for proper WWW-Authenticate responses - # Regular clients will never see the extended WWW-Authenticate headers with resource_metadata hints - header "X-Authentication-Scheme", "Bearer" header "Content-Type", "application/json" post "/mcp", request_body.to_json end From 01201a5288daa37b04a5be7d44811503ad33cc12 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 10 Feb 2026 10:58:57 +0100 Subject: [PATCH 237/293] Ignore one more rubocop offense --- app/controllers/members_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/members_controller.rb b/app/controllers/members_controller.rb index 4985c98cd45..689d0548dd5 100644 --- a/app/controllers/members_controller.rb +++ b/app/controllers/members_controller.rb @@ -84,7 +84,7 @@ class MembersController < ApplicationController per_page: params[:per_page]) end - def destroy_by_principal + def destroy_by_principal # rubocop:disable Metrics/AbcSize principal = Principal.visible.find(params[:principal_id]) service_call = Members::DeleteByPrincipalService From 3075bbd5a2dc61406ff6b72f1f626d39d123b45b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Mon, 9 Feb 2026 15:27:48 +0100 Subject: [PATCH 238/293] Rewrite setting accessors as define_methods --- app/models/setting.rb | 66 +------------------ app/models/setting/accessors.rb | 109 ++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 65 deletions(-) create mode 100644 app/models/setting/accessors.rb diff --git a/app/models/setting.rb b/app/models/setting.rb index 29f305290d5..4a02d000988 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -31,6 +31,7 @@ class Setting < ApplicationRecord class NotWritableError < StandardError; end + extend Accessors extend Aliases extend MailSettings @@ -74,71 +75,6 @@ class Setting < ApplicationRecord Big5-HKSCS TIS-620).freeze - class << self - def create_setting(name, value = {}) - ::Settings::Definition.add(name, **value.symbolize_keys) - end - - def create_setting_accessors(name) - # Defines getter and setter for each setting - # Then setting values can be read using: Setting.some_setting_name - # or set using Setting.some_setting_name = "some value" - src = <<-END_SRC - def self.#{name} - # when running too early, there is no settings table. do nothing - self[:#{name}] if settings_table_exists_yet? - end - - def self.#{name}? - # when running too early, there is no settings table. do nothing - return unless settings_table_exists_yet? - definition = Settings::Definition[:#{name}] - - if definition.format != :boolean - ActiveSupport::Deprecation.new.warn "Calling #{self}.#{name}? is deprecated since it is not a boolean", caller_locations - end - - value = self[:#{name}] - ActiveRecord::Type::Boolean.new.cast(value) || false - end - - def self.#{name}=(value) - if settings_table_exists_yet? - self[:#{name}] = value - else - logger.warn "Trying to save a setting named '#{name}' while there is no 'setting' table yet. This setting will not be saved!" - nil # when running too early, there is no settings table. do nothing - end - end - - def self.#{name}_writable? - Settings::Definition[:#{name}].writable? - end - END_SRC - class_eval src, __FILE__, __LINE__ - end - - def method_missing(method, *, &) - if exists?(accessor_base_name(method)) - create_setting_accessors(accessor_base_name(method)) - - send(method, *) - else - super - end - end - - def respond_to_missing?(method_name, include_private = false) - exists?(accessor_base_name(method_name)) || super - end - - private - - def accessor_base_name(name) - name.to_s.sub(/(_writable\?)|(\?)|=\z/, "") - end - end - validates :name, uniqueness: true, inclusion: { diff --git a/app/models/setting/accessors.rb b/app/models/setting/accessors.rb new file mode 100644 index 00000000000..7459da8b595 --- /dev/null +++ b/app/models/setting/accessors.rb @@ -0,0 +1,109 @@ +# 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 Setting + # Dynamically defines getter, setter, boolean, and writable? class methods + # for each setting. Methods are lazily created via method_missing when a + # setting is first accessed. + # + # After creation, setting values can be read using: Setting.some_setting_name + # or set using: Setting.some_setting_name = "some value" + module Accessors + def create_setting(name, value = {}) + ::Settings::Definition.add(name, **value.symbolize_keys) + end + + def create_setting_accessors(name) + define_setting_getter(name) + define_setting_boolean_getter(name) + define_setting_setter(name) + define_setting_writable_check(name) + end + + def method_missing(method, *, &) + if exists?(accessor_base_name(method)) + create_setting_accessors(accessor_base_name(method)) + + send(method, *) + else + super + end + end + + def respond_to_missing?(method_name, include_private = false) + exists?(accessor_base_name(method_name)) || super + end + + private + + def define_setting_getter(name) + define_singleton_method(name) do + # when running too early, there is no settings table. do nothing + self[name] if settings_table_exists_yet? + end + end + + def define_setting_boolean_getter(name) + define_singleton_method(:"#{name}?") do + definition = Settings::Definition[name] + + if definition.format != :boolean + ActiveSupport::Deprecation.new.warn "Calling #{self}.#{name}? is deprecated since it is not a boolean", caller_locations + end + + # Use accessor to go through same table check + value = public_send(name) + ActiveRecord::Type::Boolean.new.cast(value) || false + end + end + + def define_setting_setter(name) + define_singleton_method(:"#{name}=") do |value| + if settings_table_exists_yet? + self[name] = value + else + logger.warn "Trying to save a setting named '#{name}' while there is no 'setting' table yet. " \ + "This setting will not be saved!" + nil # when running too early, there is no settings table. do nothing + end + end + end + + def define_setting_writable_check(name) + define_singleton_method(:"#{name}_writable?") do + Settings::Definition[name].writable? + end + end + + def accessor_base_name(name) + name.to_s.sub(/(_writable\?)|(\?)|=\z/, "") + end + end +end From c65c97678e5ff0cf810b9aac88db85dc633bed55 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 10 Feb 2026 13:35:19 +0100 Subject: [PATCH 239/293] Fix loading in refresh tokens controller --- .../block_note_editor_component.rb | 2 +- .../documents/refresh_tokens_controller.rb | 11 ++++------- modules/documents/config/routes.rb | 4 ++-- .../documents/refresh_tokens_controller_spec.rb | 16 ++++++++-------- 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.rb b/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.rb index f7ee5831ad9..50397a1b0c2 100644 --- a/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.rb +++ b/modules/documents/app/components/documents/show_edit_view/block_note_editor_component.rb @@ -41,7 +41,7 @@ module Documents private def refresh_token_url - document_refresh_token_path(document) + project_document_refresh_token_path(project, document) end end end diff --git a/modules/documents/app/controllers/documents/refresh_tokens_controller.rb b/modules/documents/app/controllers/documents/refresh_tokens_controller.rb index bbaa1fa73cc..a0cc6f20cfc 100644 --- a/modules/documents/app/controllers/documents/refresh_tokens_controller.rb +++ b/modules/documents/app/controllers/documents/refresh_tokens_controller.rb @@ -30,10 +30,8 @@ module Documents class RefreshTokensController < ApplicationController - model_object Document - - before_action :find_model_object - before_action :find_project_from_association + before_action :find_project_by_project_id + before_action :find_document before_action :authorize def create @@ -52,9 +50,8 @@ module Documents private - def find_model_object(object_id = :document_id) - super - @document = @object + def find_document + @document = Document.where(project_id: @project.id).find(params[:document_id]) end end end diff --git a/modules/documents/config/routes.rb b/modules/documents/config/routes.rb index b5ad7ca3e17..92d91965aa2 100644 --- a/modules/documents/config/routes.rb +++ b/modules/documents/config/routes.rb @@ -35,6 +35,8 @@ Rails.application.routes.draw do get :menu, to: "documents/menus#show" get :search end + + resource :refresh_token, only: [:create], controller: "documents/refresh_tokens", defaults: { format: :json } end end @@ -50,8 +52,6 @@ Rails.application.routes.draw do get :render_connection_error, defaults: { format: :turbo_stream } get :render_connection_recovery, defaults: { format: :turbo_stream } end - - resource :refresh_token, only: [:create], controller: "documents/refresh_tokens", defaults: { format: :json } end scope module: :documents do diff --git a/modules/documents/spec/controllers/documents/refresh_tokens_controller_spec.rb b/modules/documents/spec/controllers/documents/refresh_tokens_controller_spec.rb index c0b80a58028..ded19da8357 100644 --- a/modules/documents/spec/controllers/documents/refresh_tokens_controller_spec.rb +++ b/modules/documents/spec/controllers/documents/refresh_tokens_controller_spec.rb @@ -45,9 +45,9 @@ RSpec.describe Documents::RefreshTokensController do describe "POST #create" do context "when user is not logged in" do it "redirects to login" do - post :create, params: { document_id: document.id } + post :create, params: { project_id: project.id, document_id: document.id } - expect(response).to redirect_to(signin_path(back_url: document_refresh_token_url(document))) + expect(response).to redirect_to(signin_path(back_url: project_document_refresh_token_url(project.id, document))) end end @@ -56,10 +56,10 @@ RSpec.describe Documents::RefreshTokensController do login_as(user) end - it "returns forbidden" do - post :create, params: { document_id: document.id } + it "returns not found" do + post :create, params: { project_id: project.id, document_id: document.id } - expect(response).to have_http_status(:forbidden) + expect(response).to have_http_status(:not_found) end end @@ -71,7 +71,7 @@ RSpec.describe Documents::RefreshTokensController do it "returns a successful JSON response" do expect do - post :create, params: { document_id: document.id } + post :create, params: { project_id: project.id, document_id: document.id } end.to change(Doorkeeper::AccessToken, :count).by(1) expect(response).to have_http_status(:ok) @@ -96,7 +96,7 @@ RSpec.describe Documents::RefreshTokensController do end it "returns not found" do - post :create, params: { document_id: 999_999 } + post :create, params: { project_id: project.id, document_id: 999_999 } expect(response).to have_http_status(:not_found) end @@ -113,7 +113,7 @@ RSpec.describe Documents::RefreshTokensController do end it "returns unprocessable entity" do - post :create, params: { document_id: document.id } + post :create, params: { project_id: project.id, document_id: document.id } expect(response).to have_http_status(:unprocessable_entity) json = response.parsed_body From 27ff0acf3a1c127482edf73a74a43f75db93ad58 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 12:32:51 -0300 Subject: [PATCH 240/293] Bump faraday from 2.14.0 to 2.14.1 (#21935) Bumps [faraday](https://github.com/lostisland/faraday) from 2.14.0 to 2.14.1. - [Release notes](https://github.com/lostisland/faraday/releases) - [Changelog](https://github.com/lostisland/faraday/blob/main/CHANGELOG.md) - [Commits](https://github.com/lostisland/faraday/compare/v2.14.0...v2.14.1) --- updated-dependencies: - dependency-name: faraday dependency-version: 2.14.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index ecb9b41deff..d6a06a53a1d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -556,7 +556,7 @@ GEM factory_bot_rails (6.5.1) factory_bot (~> 6.5) railties (>= 6.1.0) - faraday (2.14.0) + faraday (2.14.1) faraday-net_http (>= 2.0, < 3.5) json logger @@ -724,7 +724,7 @@ GEM reline (>= 0.4.2) iso8601 (0.13.0) jmespath (1.6.2) - json (2.18.0) + json (2.18.1) json-jwt (1.17.0) activesupport (>= 4.2) aes_key_wrap @@ -1881,7 +1881,7 @@ CHECKSUMS excon (1.3.2) sha256=a089babe98638e58042a7d542b2bbd183304527e33d612b6dde22fa491a544a5 factory_bot (6.5.6) sha256=12beb373214dccc086a7a63763d6718c49769d5606f0501e0a4442676917e077 factory_bot_rails (6.5.1) sha256=d3cc4851eae4dea8a665ec4a4516895045e710554d2b5ac9e68b94d351bc6d68 - faraday (2.14.0) sha256=8699cfe5d97e55268f2596f9a9d5a43736808a943714e3d9a53e6110593941cd + faraday (2.14.1) sha256=a43cceedc1e39d188f4d2cdd360a8aaa6a11da0c407052e426ba8d3fb42ef61c faraday-follow_redirects (0.5.0) sha256=5cde93c894b30943a5d2b93c2fe9284216a6b756f7af406a1e55f211d97d10ad faraday-net_http (3.4.2) sha256=f147758260d3526939bf57ecf911682f94926a3666502e24c69992765875906c fastimage (2.4.0) sha256=5fce375e27d3bdbb46c18dbca6ba9af29d3304801ae1eb995771c4796c5ac7e8 @@ -1948,7 +1948,7 @@ CHECKSUMS irb (1.16.0) sha256=2abe56c9ac947cdcb2f150572904ba798c1e93c890c256f8429981a7675b0806 iso8601 (0.13.0) sha256=298c2b15b7be5fa95a1372813d36a2257656cd8e906dfbc1f5cb409851425aa2 jmespath (1.6.2) sha256=238d774a58723d6c090494c8879b5e9918c19485f7e840f2c1c7532cf84ebcb1 - json (2.18.0) sha256=b10506aee4183f5cf49e0efc48073d7b75843ce3782c68dbeb763351c08fd505 + json (2.18.1) sha256=fe112755501b8d0466b5ada6cf50c8c3f41e897fa128ac5d263ec09eedc9f986 json-jwt (1.17.0) sha256=6ff99026b4c54281a9431179f76ceb81faa14772d710ef6169785199caadc4cc json-schema (4.3.1) sha256=d5e68dc32b94408d0b06ad04f9382ccbb6fe5a44910e066f8547f56c471a7825 json_rpc_handler (0.1.1) sha256=ea248c8cb4d5490dde320db316ac5e3caf8137a20b5ff9035a4bfc1d19438d90 From 20060a86b6fdd17613b26d487904072a3083be45 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 10 Feb 2026 18:32:36 +0100 Subject: [PATCH 241/293] Allow to access news#show globally --- app/controllers/news_controller.rb | 7 ++- config/routes.rb | 2 +- spec/controllers/news_controller_spec.rb | 72 +++++++++++++++--------- spec/routing/news_spec.rb | 6 ++ 4 files changed, 59 insertions(+), 28 deletions(-) diff --git a/app/controllers/news_controller.rb b/app/controllers/news_controller.rb index 7e5a4baa8c4..dec12d27241 100644 --- a/app/controllers/news_controller.rb +++ b/app/controllers/news_controller.rb @@ -118,6 +118,11 @@ class NewsController < ApplicationController private def find_news_object - @news = @project.news.visible.find(params[:id].to_i) + if @project + @news = @project.news.visible.find(params[:id].to_i) + else + @news = News.visible.find(params[:id].to_i) + @project = @news.project + end end end diff --git a/config/routes.rb b/config/routes.rb index 7c2922ce351..70cdd4ec084 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -911,7 +911,7 @@ Rails.application.routes.draw do # The show page of groups is public and thus moved out of the admin scope resources :groups, only: %i[show], as: :show_group - resources :news, only: %i[index] + resources :news, only: %i[index show] # redirect for backwards compatibility scope "attachments", diff --git a/spec/controllers/news_controller_spec.rb b/spec/controllers/news_controller_spec.rb index 6a63831755b..c4d262c6d81 100644 --- a/spec/controllers/news_controller_spec.rb +++ b/spec/controllers/news_controller_spec.rb @@ -35,54 +35,74 @@ RSpec.describe NewsController do include BecomeMember - let(:news) { create(:news) } + let!(:news) { create(:news, project: project) } + let!(:news_in_other_project) { create(:news) } shared_let(:project) { create(:project) } shared_current_user { create(:admin) } describe "#index" do - it "renders index" do - get :index + context "when requesting the global index" do + it "renders index" do + get :index - expect(response).to be_successful - expect(response).to render_template "index" + expect(response).to be_successful + expect(response).to render_template "index" - expect(assigns(:project)).to be_nil - expect(assigns(:news)).not_to be_nil + expect(assigns(:project)).to be_nil + expect(assigns(:news)).to contain_exactly(news, news_in_other_project) + end end - it "renders index with project" do - get :index, params: { project_id: project.id } + context "when requesting the project index" do + it "renders index with project" do + get :index, params: { project_id: project.id } - expect(response).to be_successful - expect(response).to render_template "index" - expect(assigns(:news)).not_to be_nil + expect(response).to be_successful + expect(response).to render_template "index" + expect(assigns(:news)).to contain_exactly(news) + expect(assigns(:project)).to eq(project) + end end end describe "#show" do - it "renders show" do - get :show, params: { project_id: news.project_id, id: news.id } + context "when routed through the global news path" do + it "renders show" do + get :show, params: { id: news.id } - expect(response).to be_successful - expect(response).to render_template "show" + expect(response).to be_successful + expect(response).to render_template "show" - expect(assigns(:news)).to eq news + expect(assigns(:news)).to eq news + expect(assigns(:project)).to eq news.project + end end - it "renders show with slug" do - get :show, params: { project_id: news.project_id, id: "#{news.id}-some-news-title" } + context "when routed through the project" do + it "renders show" do + get :show, params: { project_id: news.project_id, id: news.id } - expect(response).to be_successful - expect(response).to render_template "show" + expect(response).to be_successful + expect(response).to render_template "show" - expect(assigns(:news)).to eq news - end + expect(assigns(:news)).to eq news + end - it "renders error if news item is not found" do - get :show, params: { project_id: news.project_id, id: -1 } + it "renders show with slug" do + get :show, params: { project_id: news.project_id, id: "#{news.id}-some-news-title" } - expect(response).to be_not_found + expect(response).to be_successful + expect(response).to render_template "show" + + expect(assigns(:news)).to eq news + end + + it "renders error if news item is not found" do + get :show, params: { project_id: news.project_id, id: -1 } + + expect(response).to be_not_found + end end end diff --git a/spec/routing/news_spec.rb b/spec/routing/news_spec.rb index 02c34f0871e..f5a362385d9 100644 --- a/spec/routing/news_spec.rb +++ b/spec/routing/news_spec.rb @@ -42,6 +42,12 @@ RSpec.describe NewsController, "routing" do format: "atom") end + it do + expect(subject).to route(:get, "/news/123").to(controller: "news", + action: "show", + id: "123") + end + context "with project scoped routes" do it do expect(subject).to route(:get, "/projects/567/news").to(controller: "news", From cf241951eb5d32787a87bb2fde7d8b187d6f6fb9 Mon Sep 17 00:00:00 2001 From: ulferts Date: Tue, 10 Feb 2026 18:34:39 +0100 Subject: [PATCH 242/293] fix custom field params broken by rails 8.1 leading bracket change MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rails 8.1 removed deprecated support for stripping leading brackets in parameter names (rails/rails#53471). Previously, a form field named `[custom_field_values][123]` was parsed as `{ "custom_field_values" => { "123" => "" } }`. In Rails 8.1, it is parsed as `{ "[custom_field_values]" => { "123" => "" } }`, making the custom field values invisible to `params.fetch(:custom_field_values, {})`. `custom_field_tag_for_bulk_edit` was called with an empty string as the `name` prefix (e.g. in the work package move form), producing field names like `[custom_field_values][42]` — starting with a leading bracket. This caused required custom field validation to be silently skipped during moves, allowing work packages to be moved without filling in required fields. The fix avoids the leading bracket by emitting `custom_field_values[42]` when no name prefix is given. --- app/helpers/custom_fields_helper.rb | 2 +- spec/features/work_packages/bulk/move_work_package_spec.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/helpers/custom_fields_helper.rb b/app/helpers/custom_fields_helper.rb index 3b83bf35ab4..b5f68de591d 100644 --- a/app/helpers/custom_fields_helper.rb +++ b/app/helpers/custom_fields_helper.rb @@ -72,7 +72,7 @@ module CustomFieldsHelper end def custom_field_tag_for_bulk_edit(name, custom_field, project = nil) # rubocop:disable Metrics/AbcSize - field_name = "#{name}[custom_field_values][#{custom_field.id}]" + field_name = name.present? ? "#{name}[custom_field_values][#{custom_field.id}]" : "custom_field_values[#{custom_field.id}]" field_id = "#{name}_custom_field_values_#{custom_field.id}" field_format = OpenProject::CustomFieldFormat.find_by(name: custom_field.field_format) diff --git a/spec/features/work_packages/bulk/move_work_package_spec.rb b/spec/features/work_packages/bulk/move_work_package_spec.rb index d06e6fb494c..d4eacbc5553 100644 --- a/spec/features/work_packages/bulk/move_work_package_spec.rb +++ b/spec/features/work_packages/bulk/move_work_package_spec.rb @@ -162,6 +162,8 @@ RSpec.describe "Moving a work package through Rails view", :js do it "does not moves the work package when the required field is missing" do select "Risk", from: "Type" expect(page).to have_field(required_cf.name) + project_autocompleter = find_test_selector("new_project_id") + expect_current_autocompleter_value(project_autocompleter, "Target") # Clicking move and follow might be broken due to the location.href # in the refresh-on-form-changes component From 21ac8802ab4b2c95b57e8b1c5c281edef58f024c Mon Sep 17 00:00:00 2001 From: OpenProject Actions CI Date: Wed, 11 Feb 2026 04:01:40 +0000 Subject: [PATCH 243/293] update locales from crowdin [ci skip] --- config/locales/crowdin/af.yml | 3 +- config/locales/crowdin/ar.yml | 3 +- config/locales/crowdin/az.yml | 3 +- config/locales/crowdin/be.yml | 3 +- config/locales/crowdin/bg.yml | 3 +- config/locales/crowdin/ca.yml | 3 +- config/locales/crowdin/ckb-IR.yml | 3 +- config/locales/crowdin/cs.yml | 3 +- config/locales/crowdin/da.yml | 3 +- config/locales/crowdin/de.yml | 41 +++++++++---------- config/locales/crowdin/el.yml | 3 +- config/locales/crowdin/eo.yml | 3 +- config/locales/crowdin/es.yml | 3 +- config/locales/crowdin/et.yml | 3 +- config/locales/crowdin/eu.yml | 3 +- config/locales/crowdin/fa.yml | 3 +- config/locales/crowdin/fi.yml | 3 +- config/locales/crowdin/fil.yml | 3 +- config/locales/crowdin/fr.yml | 3 +- config/locales/crowdin/he.yml | 3 +- config/locales/crowdin/hi.yml | 3 +- config/locales/crowdin/hr.yml | 3 +- config/locales/crowdin/hu.yml | 3 +- config/locales/crowdin/id.yml | 3 +- config/locales/crowdin/it.yml | 3 +- config/locales/crowdin/ja.yml | 3 +- config/locales/crowdin/ka.yml | 3 +- config/locales/crowdin/kk.yml | 3 +- config/locales/crowdin/ko.yml | 3 +- config/locales/crowdin/lt.yml | 3 +- config/locales/crowdin/lv.yml | 3 +- config/locales/crowdin/mn.yml | 3 +- config/locales/crowdin/ms.yml | 3 +- config/locales/crowdin/ne.yml | 3 +- config/locales/crowdin/nl.yml | 3 +- config/locales/crowdin/no.yml | 3 +- config/locales/crowdin/pl.yml | 3 +- config/locales/crowdin/pt-BR.yml | 3 +- config/locales/crowdin/pt-PT.yml | 3 +- config/locales/crowdin/ro.yml | 3 +- config/locales/crowdin/ru.yml | 3 +- config/locales/crowdin/rw.yml | 3 +- config/locales/crowdin/si.yml | 3 +- config/locales/crowdin/sk.yml | 3 +- config/locales/crowdin/sl.yml | 3 +- config/locales/crowdin/sr.yml | 3 +- config/locales/crowdin/sv.yml | 3 +- config/locales/crowdin/th.yml | 3 +- config/locales/crowdin/tr.yml | 3 +- config/locales/crowdin/uk.yml | 3 +- config/locales/crowdin/uz.yml | 3 +- config/locales/crowdin/vi.yml | 3 +- config/locales/crowdin/zh-CN.yml | 3 +- config/locales/crowdin/zh-TW.yml | 3 +- modules/meeting/config/locales/crowdin/de.yml | 22 +++++----- .../storages/config/locales/crowdin/de.yml | 22 +++++----- 56 files changed, 95 insertions(+), 149 deletions(-) diff --git a/config/locales/crowdin/af.yml b/config/locales/crowdin/af.yml index 3c6b45f8ae5..2b648c4bb7a 100644 --- a/config/locales/crowdin/af.yml +++ b/config/locales/crowdin/af.yml @@ -1036,7 +1036,6 @@ af: title: "Workflow missing for work package sharing" message: "No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role." link_message: "Configure the workflows in the administration." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3217,7 +3216,6 @@ af: label_duplicate: "duplicate" label_duplicates: "duplikate" label_edit: "Redigeer" - label_edit_attribute: "Edit attribute" label_edit_x: "Edit: %{x}" label_enable_multi_select: "Wissel multikies" label_enabled_project_custom_fields: "Enabled custom fields" @@ -4134,6 +4132,7 @@ af: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/ar.yml b/config/locales/crowdin/ar.yml index 8eb09a62ec1..ab600472ebc 100644 --- a/config/locales/crowdin/ar.yml +++ b/config/locales/crowdin/ar.yml @@ -1072,7 +1072,6 @@ ar: title: "Workflow missing for work package sharing" message: "No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role." link_message: "Configure the workflows in the administration." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3413,7 +3412,6 @@ ar: label_duplicate: "مكرر" label_duplicates: "التكرارات" label_edit: "تعديل" - label_edit_attribute: "Edit attribute" label_edit_x: "Edit: %{x}" label_enable_multi_select: "تبديل متعدد الخيارات" label_enabled_project_custom_fields: "تمكين الحقول المخصصة" @@ -4338,6 +4336,7 @@ ar: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/az.yml b/config/locales/crowdin/az.yml index cec69d8d0cc..393332671a6 100644 --- a/config/locales/crowdin/az.yml +++ b/config/locales/crowdin/az.yml @@ -1036,7 +1036,6 @@ az: title: "Workflow missing for work package sharing" message: "No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role." link_message: "Configure the workflows in the administration." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3217,7 +3216,6 @@ az: label_duplicate: "duplicate" label_duplicates: "duplicates" label_edit: "Düzəliş et" - label_edit_attribute: "Edit attribute" label_edit_x: "Edit: %{x}" label_enable_multi_select: "Toggle multiselect" label_enabled_project_custom_fields: "Enabled custom fields" @@ -4134,6 +4132,7 @@ az: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/be.yml b/config/locales/crowdin/be.yml index 4a6959a0ab5..e2b4a93b2a9 100644 --- a/config/locales/crowdin/be.yml +++ b/config/locales/crowdin/be.yml @@ -1054,7 +1054,6 @@ be: title: "Workflow missing for work package sharing" message: "No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role." link_message: "Configure the workflows in the administration." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3315,7 +3314,6 @@ be: label_duplicate: "duplicate" label_duplicates: "duplicates" label_edit: "Рэдагаваць" - label_edit_attribute: "Edit attribute" label_edit_x: "Edit: %{x}" label_enable_multi_select: "Toggle multiselect" label_enabled_project_custom_fields: "Enabled custom fields" @@ -4236,6 +4234,7 @@ be: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/bg.yml b/config/locales/crowdin/bg.yml index 8f569acf5a3..2adffd6c54b 100644 --- a/config/locales/crowdin/bg.yml +++ b/config/locales/crowdin/bg.yml @@ -1036,7 +1036,6 @@ bg: title: "Workflow missing for work package sharing" message: "No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role." link_message: "Configure the workflows in the administration." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3217,7 +3216,6 @@ bg: label_duplicate: "дубликат" label_duplicates: "дублирания" label_edit: "Редактиране" - label_edit_attribute: "Edit attribute" label_edit_x: "Редактиране: %{x}" label_enable_multi_select: "Превключване към множествен избор" label_enabled_project_custom_fields: "Разрешени потребителски полета" @@ -4134,6 +4132,7 @@ bg: permission_edit_project_query: "Редактиране на заявката за проект" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/ca.yml b/config/locales/crowdin/ca.yml index 51bc1afed65..b31ae687569 100644 --- a/config/locales/crowdin/ca.yml +++ b/config/locales/crowdin/ca.yml @@ -1033,7 +1033,6 @@ ca: title: "Falta un flux de treball per compartir paquets de treball" message: "No s'ha configurat cap flux de treball per a la funció \"Editor de paquets de treball\". Sense un flux de treball, el que es comparteix amb l'usuari no pot alterar l'estat del paquet de treball. Els fluxos de treball es poden copiar. Seleccioneu un tipus d'origen (p. ex., \"Tasca\") i una funció d'origen (p. ex., \"Membre\"). A continuació, seleccioneu els tipus d'objectius. Per començar, podeu seleccionar tots els tipus com a objectius. Finalment, seleccioneu la funció \"Editor de paquets de treball\" com a objectiu i premeu \"Copia\". Després d'haver creat els valors predeterminats, ajusteu els fluxos de treball com ho feu per a qualsevol altra funció." link_message: "Configura els fluxos de treball a l'administració." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3214,7 +3213,6 @@ ca: label_duplicate: "duplicats" label_duplicates: "duplicats" label_edit: "Editar" - label_edit_attribute: "Edit attribute" label_edit_x: "Edita: %{x}" label_enable_multi_select: "Activa/desactiva selecció múltiple" label_enabled_project_custom_fields: "Habilita camps personalitzats" @@ -4127,6 +4125,7 @@ ca: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/ckb-IR.yml b/config/locales/crowdin/ckb-IR.yml index 993df024c5a..0ed3514ec98 100644 --- a/config/locales/crowdin/ckb-IR.yml +++ b/config/locales/crowdin/ckb-IR.yml @@ -1036,7 +1036,6 @@ ckb-IR: title: "Workflow missing for work package sharing" message: "No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role." link_message: "Configure the workflows in the administration." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3217,7 +3216,6 @@ ckb-IR: label_duplicate: "duplicate" label_duplicates: "duplicates" label_edit: "Edit" - label_edit_attribute: "Edit attribute" label_edit_x: "Edit: %{x}" label_enable_multi_select: "Toggle multiselect" label_enabled_project_custom_fields: "Enabled custom fields" @@ -4134,6 +4132,7 @@ ckb-IR: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/cs.yml b/config/locales/crowdin/cs.yml index 6e5311a4174..86a9c1d2675 100644 --- a/config/locales/crowdin/cs.yml +++ b/config/locales/crowdin/cs.yml @@ -1054,7 +1054,6 @@ cs: title: "Pro sdílení pracovního balíčku chybí pracovní postup" message: "Pro roli 'Pracovní balíček' není nastaven žádný pracovní postup. Bez pracovního postupu nemůže sdílení s uživatelem změnit stav pracovního balíčku. Pracovní toky mohou být zkopírovány. Vyberte typ zdroje (např. 'Úkol') a zdrojovou roli (např. 'Člen'). Pak vyberte cílové typy. Na začátku můžete vybrat všechny typy jako cíle. Nakonec vyberte roli 'Editor pracovních balíčků' jako cíl a stiskněte 'Kopírovat'. Poté, co jste tak vytvořili výchozí nastavení, vyladit pracovní postupy, jak to děláte pro každou jinou roli." link_message: "Konfigurace pracovních postupů v administraci." - templated_subject_hint: Automaticky generováno pomocí typu %{type} summary: reports: category: @@ -3315,7 +3314,6 @@ cs: label_duplicate: "duplikovat" label_duplicates: "duplikovaů" label_edit: "Upravit" - label_edit_attribute: "Edit attribute" label_edit_x: "Edit: %{x}" label_enable_multi_select: "Přepnout multiselect" label_enabled_project_custom_fields: "Povoleno volitelné pole" @@ -4235,6 +4233,7 @@ cs: permission_edit_project_query: "Upravit dotaz projektu" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/da.yml b/config/locales/crowdin/da.yml index 76c63ac1c1f..a7cedf2e9b7 100644 --- a/config/locales/crowdin/da.yml +++ b/config/locales/crowdin/da.yml @@ -1034,7 +1034,6 @@ da: title: "Workflow missing for work package sharing" message: "No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role." link_message: "Configure the workflows in the administration." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3215,7 +3214,6 @@ da: label_duplicate: "duplicate" label_duplicates: "dubletter" label_edit: "Redigér" - label_edit_attribute: "Edit attribute" label_edit_x: "Edit: %{x}" label_enable_multi_select: "Vælg multivalg" label_enabled_project_custom_fields: "Aktiverede brugerdefinerede felter" @@ -4132,6 +4130,7 @@ da: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/de.yml b/config/locales/crowdin/de.yml index aa2725f3940..1c4647d446f 100644 --- a/config/locales/crowdin/de.yml +++ b/config/locales/crowdin/de.yml @@ -113,7 +113,7 @@ de: index: description: "Das Model Context Protocol ermöglicht es KI-Agenten, ihren Nutzern Tools und Ressourcen bereitzustellen, die diese OpenProject-Instanz zur Verfügung stellt." resources_heading: "Ressourcen" - resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject stellt die folgenden Ressourcen bereit. Jede Ressource kann nach Wunsch aktiviert, umbenannt und beschrieben werden. Weitere Informationen finden sich in der [Dokumentation zu MCP-Ressourcen](docs_url)." resources_submit: "Ressourcen aktualisieren" tools_heading: "Tools" tools_description: "OpenProject stellt die folgenden Tools bereit. Jedes Tool kann nach Wunsch aktiviert, umbenannt und beschrieben werden. Weitere Informationen finden sich in der [Dokumentation zu MCP-Ressourcen](docs_url)." @@ -122,7 +122,7 @@ de: success: "MCP-Konfigurationen wurden erfolgreich aktualisiert." server_form: description_caption: "Wie der MCP-Server gegenüber anderen Anwendungen beschrieben wird, die sich damit verbinden." - title_caption: "A short title shown to applications that connect to the MCP server." + title_caption: "Ein kurzer Titel, der Anwendungen angezeigt wird, die sich mit dem MCP-Server verbinden." update: failure: "Die MCP-Konfiguration konnte nicht aktualisiert werden." success: "Die MCP-Konfiguration wurde erfolgreich aktualisiert." @@ -605,7 +605,7 @@ de: is_for_all_blank_slate: heading: Für alle Projekte description: Dieses Projekt-Attribut ist in allen Projekten aktiviert, da die Option "Für alle Projekte" aktiviert ist. Es kann nicht für einzelne Projekte deaktiviert werden. - enabled_via_assignee_when_submitted_html: This project attribute cannot be disabled since it is set as assignee when submitted for project initiation requests. + enabled_via_assignee_when_submitted_html: Dieses Projektattribut kann nicht deaktiviert werden, da es bei Projektinitiierungsanträgen als Zuweisung bei Einreichung verwendet wird. types: no_results_title_text: Derzeit stehen keine Typen zur Verfügung. form: @@ -633,7 +633,7 @@ de: label_request_submission: "Einreichung des Antrags" project_attributes_description: > Wählen Sie aus, welche Attribute in den Projektinitiierungsantrag aufgenommen werden sollen. Diese Liste zeigt nur [Projektattribute](project_attributes_url), die für dieses Projekt aktiviert sind. - enabled_because_required_html: This project attribute cannot be disabled for this project initiation request since it is defined as required. This can be changed in the administration settings by the administrator of the instance. + enabled_because_required_html: Dieses Projektattribut kann für diesen Projektinitiierungsantrag nicht deaktiviert werden, da es als erforderlich definiert ist. Dies kann in den Administrationseinstellungen vom Administrator der Instanz geändert werden. status: button_edit: Status bearbeiten wizard: @@ -1029,7 +1029,6 @@ de: title: "Der Workflow für das Teilen von Arbeitspaketen fehlt" message: "Es ist kein Workflow für die Rolle ‚Work package editor‘ konfiguriert. Nur mit einem solchen Workflow können Benutzer, mit denen ein Arbeitspaket geteilt wurde, den Status des Arbeitspakets ändern. Workflows lassen sich einfach kopieren. Wählen Sie dazu einen Quell-Typ (z. B. ‚Task‘) und eine Quell-Rolle (z. B. 'Member') aus. Wählen Sie dann die Ziel-Typen aus. Als ersten Schritt können Sie alle Typen als Ziel-Typen auswählen. Danach wählen Sie die Ziel-Rolle ‚Work package editor‘ aus und drücken Sie auf den Knopf ‚Kopieren‘. Nachdem Sie hiermit eine Grundlage geschaffen haben, können Sie danach diese Workflows weiter anpassen, ganz genau wie Sie es für jede andere Rolle bereits getan haben." link_message: "Konfigurieren Sie die Workflows in der Administration." - templated_subject_hint: Automatisch durch den Typ %{type} erzeugt summary: reports: category: @@ -1248,9 +1247,9 @@ de: port: "Port" tls_certificate_string: "LDAP-Server SSL-Zertifikat" mcp_configuration: - enabled: Enabled - title: Title - description: Description + enabled: Aktiviert + title: Titel + description: Beschreibung member: roles: "Rollen" notification: @@ -2458,12 +2457,12 @@ de: edit_attribute_groups: Attributgruppen bearbeiten gantt_pdf_export: Gantt PDF Export ldap_groups: LDAP-Benutzer- und Gruppensynchronisation - mcp_server: MCP Server + mcp_server: MCP-Server nextcloud_sso: Single Sign-On für Nextcloud-Speicher one_drive_sharepoint_file_storage: OneDrive/SharePoint-Datei-Speicher placeholder_users: Platzhalter-Benutzer portfolio_management: Portfolioverwaltung - project_creation_wizard: Project initiation request + project_creation_wizard: Projektinitiierungsantrag project_list_sharing: Projektlistenfreigabe readonly_work_packages: Schreibgeschützte Arbeitspakete scim_api: SCIM-Server-API @@ -2536,7 +2535,7 @@ de: title: "Benutzerdefinierte Aktionen" description: "Selbstdefinierte Aktionen sind Verknüpfungen zu einer Reihe von vordefinierten Aktionen, die Sie für bestimmte Arbeitspakete je nach Status, Rolle, Typ oder Projekt mit nur einem Klick auf einen Button auslösen." mcp_server: - description: "Integrate AI agents with your OpenProject instance through MCP." + description: "Integrieren Sie KI-Agenten mit Ihrer OpenProject-Instanz über MCP." nextcloud_sso: title: "Single Sign-On für Nextcloud-Speicher" description: "Aktivieren Sie nahtlose und sichere Authentifizierung für Ihren Nextcloud-Speicher mit Single Sign-On. Vereinfachen Sie das Zugriffsmanagement und erhöhen Sie den Benutzerkomfort." @@ -2549,7 +2548,7 @@ de: virus_scanning: description: "Stellen Sie sicher, dass hochgeladene Dateien in OpenProject auf Viren gescannt werden, bevor sie für andere Benutzer zugänglich sind." project_creation_wizard: - description: "Generate a step-by-step wizard to help project managers fill out a project initiation request." + description: "Generieren Sie einen Schritt-für-Schritt-Assistenten, um Projektmanagern dabei zu helfen, einen Projektinitiierungsantrag auszufüllen." placeholder_users: title: Platzhalter-Konten description: > @@ -2853,7 +2852,7 @@ de: new_features_list: line_0: Automatisierte Projektinitiierung (Enterprise Add-on). line_1: "Besprechungen: Fügen Sie neue oder bestehende Arbeitspakete als Ergebnisse hinzu." - line_2: "Meetings: show iCal responses in OpenProject." + line_2: "Besprechungen: iCal-Antworten in OpenProject anzeigen." line_3: "Wiederkehrende Besprechungen: Kopieren Sie Tagesordnungspunkte zur nächsten Besprechung." line_4: "Freigabe für die Community-Edition: Hervorhebung von Attributen." line_5: Warnung vor dem Öffnen externer Links in von Benutzern erstellten Texten (Enterprise Add-on). @@ -2925,7 +2924,7 @@ de: instructions_after_error: "Sie können versuchen sich erneut anzumelden indem Sie auf %{signin} klicken. Wenn der Fehler weiterhin auftritt, fragen Sie Ihren Administrator um Hilfe." menus: admin: - ai: "Artificial Intelligence (AI)" + ai: "Künstliche Intelligenz (AI)" aggregation: "Zusammenfassungen" api_and_webhooks: "API und Webhooks" mail_notification: "Mailbenachrichtigung" @@ -2958,7 +2957,7 @@ de: text_hint: "API-Token erlauben es Drittanbieter-Anwendungen, mit dieser OpenProject-Instanz über REST-APIs zu kommunizieren." static_token_name: "API-Token" disabled_text: "API-Token sind vom Administrator nicht aktiviert. Bitte kontaktieren Sie Ihren Administrator, um diese Funktion zu nutzen." - add_button: "API token" + add_button: "API-Token" ical: blank_description: "Um einen iCalendar-Token hinzuzufügen, abonnieren Sie einen neuen oder bestehenden Kalender innerhalb des Kalender-Moduls eines Projekts. Sie müssen über die erforderlichen Berechtigungen verfügen." blank_title: "Kein iCalendar-Token" @@ -2986,7 +2985,7 @@ de: removed: "OAuth-Client-Token erfolgreich entfernt" unknown_integration: "Unbekannt" token/rss: - add_button: "RSS token" + add_button: "RSS-Token" blank_description: "Es gibt noch kein RSS-Token. Sie können eines erstellen, indem Sie auf die Schaltfläche unten klicken." blank_title: "Kein RSS-Token" title: "RSS" @@ -3056,7 +3055,7 @@ de: label_always_visible: "Immer angezeigt" label_announcement: "Ankündigung" label_angular: "AngularJS" - label_app_modules: "%{app_title} modules" + label_app_modules: "%{app_title} Module" label_api_access_key: "API-Zugriffsschlüssel" label_api_access_key_created_on: "Der API-Zugriffsschlüssel wurde vor %{value} erstellt" label_api_access_key_type: "Schnittstelle (API)" @@ -3209,7 +3208,6 @@ de: label_duplicate: "Duplikat" label_duplicates: "Duplikat von" label_edit: "Bearbeiten" - label_edit_attribute: "Attribut bearbeiten" label_edit_x: "Bearbeiten: %{x}" label_enable_multi_select: "Mehrfachauswahl umschalten" label_enabled_project_custom_fields: "Aktivierte benutzerdefinierte Felder" @@ -3964,7 +3962,7 @@ de: notice_successful_delete: "Erfolgreich gelöscht." notice_successful_cancel: "Erfolgreiche Absage." notice_successful_update: "Erfolgreich aktualisiert." - notice_successful_move: "Successful move from %{from} to %{to}." + notice_successful_move: "Erfolgreich von %{from} nach %{to} verschoben." notice_unsuccessful_create: "Erstellung fehlgeschlagen." notice_unsuccessful_create_with_reason: "Erstellung fehlgeschlagen: %{reason}" notice_unsuccessful_update: "Aktualisierung fehlgeschlagen." @@ -4126,6 +4124,7 @@ de: permission_edit_project_query: "Projektabfrage bearbeiten" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "Keine Portfolios" @@ -4310,9 +4309,9 @@ de: setting_capture_external_links: "Externe Links abfangen" setting_capture_external_links_text: > Wenn diese Funktion aktiviert ist, werden alle externen Links in formatiertem Text auf eine vor dem Link warnende Seite in der Applikation umgeleitet, bevor sie die Anwendung verlassen. Dies hilft, Benutzer vor potenziell bösartigen externen Websites zu schützen. - setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login: "Benutzer müssen angemeldet sein" setting_capture_external_links_require_login_text: > - When enabled, users wanting to click on external links need to be logged in before being able to continue. + Wenn aktiviert, müssen Benutzer angemeldet sein, um externe Links anklicken und fortfahren zu können. setting_after_first_login_redirect_url: "Weiterleitung nach erster Anmeldung" setting_after_first_login_redirect_url_text_html: > Legen Sie einen Pfad fest, an den Nutzer:innen nach der ersten Anmeldung weitergeleitet werden. Wenn leer, führt er auf die Startseite des Onboarding-Tours.
    Beispiel: /meine/seite diff --git a/config/locales/crowdin/el.yml b/config/locales/crowdin/el.yml index 729d601637e..a03baf9e074 100644 --- a/config/locales/crowdin/el.yml +++ b/config/locales/crowdin/el.yml @@ -1032,7 +1032,6 @@ el: title: "Workflow missing for work package sharing" message: "No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role." link_message: "Configure the workflows in the administration." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3213,7 +3212,6 @@ el: label_duplicate: "αντιγραφή" label_duplicates: "αντίγραφα" label_edit: "Επεξεργασία" - label_edit_attribute: "Edit attribute" label_edit_x: "Επεξεργασία: %{x}" label_enable_multi_select: "Ενεργοποίηση πολλαπλής επιλογής" label_enabled_project_custom_fields: "Ενεργοποίηση προσαρμοσμένων πεδίων" @@ -4129,6 +4127,7 @@ el: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/eo.yml b/config/locales/crowdin/eo.yml index 4f14624e04d..21b218f5c67 100644 --- a/config/locales/crowdin/eo.yml +++ b/config/locales/crowdin/eo.yml @@ -1036,7 +1036,6 @@ eo: title: "Workflow missing for work package sharing" message: "No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role." link_message: "Configure the workflows in the administration." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3217,7 +3216,6 @@ eo: label_duplicate: "duobligi" label_duplicates: "duobligoj" label_edit: "Redakti" - label_edit_attribute: "Edit attribute" label_edit_x: "Edit: %{x}" label_enable_multi_select: "Baskuligi plurelekton" label_enabled_project_custom_fields: "Ŝalti proprajn kampojn" @@ -4134,6 +4132,7 @@ eo: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/es.yml b/config/locales/crowdin/es.yml index 5153a13aa2e..d236dcd7233 100644 --- a/config/locales/crowdin/es.yml +++ b/config/locales/crowdin/es.yml @@ -1034,7 +1034,6 @@ es: title: "Falta el flujo de trabajo para compartir paquetes de trabajo" message: "Ningún flujo de trabajo está configurado para el rol 'Editor de paquetes de trabajo'. Sin un flujo de trabajo, el usuario compartido no puede alterar el estado del paquete de trabajo. Los flujos de trabajo pueden ser copiados. Seleccione un tipo de base (por ejemplo, 'Tarea') y el rol de base (por ejemplo, 'Miembro'). Luego seleccione los tipos de destino. Para empezar, puede seleccionar todos los tipos como objetivos. Por último, seleccione el papel de \"Editor de paquetes de trabajo\" como objetivo y presione \"Copiar\". Después de haber creado así los valores predeterminados, ajuste los flujos de trabajo como lo hace para cualquier otro rol." link_message: "Configure un flujo de trabajo en la administración." - templated_subject_hint: Generado automáticamente a través del tipo %{type} summary: reports: category: @@ -3214,7 +3213,6 @@ es: label_duplicate: "duplicar" label_duplicates: "duplicados" label_edit: "Editar" - label_edit_attribute: "Editar atributo" label_edit_x: "Editar: %{x}" label_enable_multi_select: "Selección multiple" label_enabled_project_custom_fields: "Habilitar campos personalizados" @@ -4130,6 +4128,7 @@ es: permission_edit_project_query: "Editar vistas de proyecto" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 carteras" diff --git a/config/locales/crowdin/et.yml b/config/locales/crowdin/et.yml index 5ccff4287cc..7f98e706cc8 100644 --- a/config/locales/crowdin/et.yml +++ b/config/locales/crowdin/et.yml @@ -1036,7 +1036,6 @@ et: title: "Workflow missing for work package sharing" message: "No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role." link_message: "Configure the workflows in the administration." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3217,7 +3216,6 @@ et: label_duplicate: "duplicate" label_duplicates: "duplikaadid" label_edit: "Muuda" - label_edit_attribute: "Edit attribute" label_edit_x: "Edit: %{x}" label_enable_multi_select: "Võimalda mitmene valik" label_enabled_project_custom_fields: "Enabled custom fields" @@ -4134,6 +4132,7 @@ et: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/eu.yml b/config/locales/crowdin/eu.yml index 715cdbfeefa..5140b5500b5 100644 --- a/config/locales/crowdin/eu.yml +++ b/config/locales/crowdin/eu.yml @@ -1036,7 +1036,6 @@ eu: title: "Workflow missing for work package sharing" message: "No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role." link_message: "Configure the workflows in the administration." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3217,7 +3216,6 @@ eu: label_duplicate: "duplicate" label_duplicates: "duplicates" label_edit: "Edit" - label_edit_attribute: "Edit attribute" label_edit_x: "Edit: %{x}" label_enable_multi_select: "Toggle multiselect" label_enabled_project_custom_fields: "Enabled custom fields" @@ -4134,6 +4132,7 @@ eu: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/fa.yml b/config/locales/crowdin/fa.yml index 570e2b041ef..ecd77bbf92f 100644 --- a/config/locales/crowdin/fa.yml +++ b/config/locales/crowdin/fa.yml @@ -1036,7 +1036,6 @@ fa: title: "Workflow missing for work package sharing" message: "No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role." link_message: "Configure the workflows in the administration." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3217,7 +3216,6 @@ fa: label_duplicate: "duplicate" label_duplicates: "duplicates" label_edit: "ویرایش" - label_edit_attribute: "Edit attribute" label_edit_x: "Edit: %{x}" label_enable_multi_select: "Toggle multiselect" label_enabled_project_custom_fields: "Enabled custom fields" @@ -4134,6 +4132,7 @@ fa: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/fi.yml b/config/locales/crowdin/fi.yml index b6f0a735a2a..50ad712f783 100644 --- a/config/locales/crowdin/fi.yml +++ b/config/locales/crowdin/fi.yml @@ -1036,7 +1036,6 @@ fi: title: "Workflow missing for work package sharing" message: "No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role." link_message: "Configure the workflows in the administration." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3217,7 +3216,6 @@ fi: label_duplicate: "kaksoiskappale" label_duplicates: "kaksoiskappaleet" label_edit: "Muokkaa" - label_edit_attribute: "Edit attribute" label_edit_x: "Edit: %{x}" label_enable_multi_select: "Vaihda monivalinta" label_enabled_project_custom_fields: "Käytössä olevat mukautetut kentät" @@ -4134,6 +4132,7 @@ fi: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/fil.yml b/config/locales/crowdin/fil.yml index 42b53e050ff..3dfa8712c85 100644 --- a/config/locales/crowdin/fil.yml +++ b/config/locales/crowdin/fil.yml @@ -1036,7 +1036,6 @@ fil: title: "Workflow missing for work package sharing" message: "No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role." link_message: "Configure the workflows in the administration." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3217,7 +3216,6 @@ fil: label_duplicate: "gayahin" label_duplicates: "mga ginaya" label_edit: "I-edit" - label_edit_attribute: "Edit attribute" label_edit_x: "Edit: %{x}" label_enable_multi_select: "Toggle multi select" label_enabled_project_custom_fields: "Pinagana ang mga custom na patlang" @@ -4134,6 +4132,7 @@ fil: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/fr.yml b/config/locales/crowdin/fr.yml index 93665f731d4..134108757d0 100644 --- a/config/locales/crowdin/fr.yml +++ b/config/locales/crowdin/fr.yml @@ -1034,7 +1034,6 @@ fr: title: "Flux de travail manquant pour le partage de lots de travaux" message: "Aucun flux de travail n'est configuré pour le rôle 'Éditeur de lots de travaux'. Sans flux de travail, le partage avec l'utilisateur ne permet pas de modifier l'état du lot de travaux. Les flux de travail peuvent être copiés. Sélectionnez un type de source (par exemple 'Tâche') et un rôle de source (par exemple 'Membre'). Sélectionnez ensuite les types cibles. Pour commencer, vous pouvez sélectionner tous les types comme cibles. Enfin, sélectionnez le rôle 'Éditeur de lot de travaux' comme cible et cliquez sur 'Copier'. Après avoir ainsi créé les valeurs par défaut, affinez les flux de travail comme vous le faites pour tous les autres rôles." link_message: "Configurez les flux de travail dans l'administration." - templated_subject_hint: Généré automatiquement par le type %{type} summary: reports: category: @@ -3215,7 +3214,6 @@ fr: label_duplicate: "dupliquer" label_duplicates: "Doublons" label_edit: "Éditer" - label_edit_attribute: "Modifier l'attribut" label_edit_x: "Modifier : %{x}" label_enable_multi_select: "Basculer multisélection" label_enabled_project_custom_fields: "Champs personnalisés activés" @@ -4132,6 +4130,7 @@ fr: permission_edit_project_query: "Modifier la requête du projet" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portefeuille" diff --git a/config/locales/crowdin/he.yml b/config/locales/crowdin/he.yml index b345305c6f7..282be8b9168 100644 --- a/config/locales/crowdin/he.yml +++ b/config/locales/crowdin/he.yml @@ -1054,7 +1054,6 @@ he: title: "Workflow missing for work package sharing" message: "No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role." link_message: "Configure the workflows in the administration." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3315,7 +3314,6 @@ he: label_duplicate: "duplicate" label_duplicates: "פריטים כפולים" label_edit: "עריכה" - label_edit_attribute: "Edit attribute" label_edit_x: "Edit: %{x}" label_enable_multi_select: "בטל בחירה מרובה" label_enabled_project_custom_fields: "Enabled custom fields" @@ -4236,6 +4234,7 @@ he: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/hi.yml b/config/locales/crowdin/hi.yml index db51a05b0d0..15bc0863369 100644 --- a/config/locales/crowdin/hi.yml +++ b/config/locales/crowdin/hi.yml @@ -1034,7 +1034,6 @@ hi: title: "Workflow missing for work package sharing" message: "No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role." link_message: "Configure the workflows in the administration." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3215,7 +3214,6 @@ hi: label_duplicate: "duplicate" label_duplicates: "duplicates" label_edit: "संपादित करें" - label_edit_attribute: "Edit attribute" label_edit_x: "Edit: %{x}" label_enable_multi_select: "Toggle multiselect" label_enabled_project_custom_fields: "Enabled custom fields" @@ -4132,6 +4130,7 @@ hi: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/hr.yml b/config/locales/crowdin/hr.yml index 0b8be38cd98..b8a6279c4a8 100644 --- a/config/locales/crowdin/hr.yml +++ b/config/locales/crowdin/hr.yml @@ -1045,7 +1045,6 @@ hr: title: "Workflow missing for work package sharing" message: "No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role." link_message: "Configure the workflows in the administration." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3266,7 +3265,6 @@ hr: label_duplicate: "duplicate" label_duplicates: "duplikati" label_edit: "Uredi" - label_edit_attribute: "Edit attribute" label_edit_x: "Edit: %{x}" label_enable_multi_select: "Aktiviraj/deaktiviraj selekciju" label_enabled_project_custom_fields: "Omogućena prilagođena polja" @@ -4185,6 +4183,7 @@ hr: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/hu.yml b/config/locales/crowdin/hu.yml index 8063218466a..6038a6c4bee 100644 --- a/config/locales/crowdin/hu.yml +++ b/config/locales/crowdin/hu.yml @@ -1035,7 +1035,6 @@ hu: title: "Workflow missing for work package sharing" message: "No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role." link_message: "Configure the workflows in the administration." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3216,7 +3215,6 @@ hu: label_duplicate: "duplikált" label_duplicates: "Ismétlődések" label_edit: "Szerkesztés" - label_edit_attribute: "Edit attribute" label_edit_x: "Szerkesztés: %{x}" label_enable_multi_select: "Multiselect ki-/ bekapcsolása" label_enabled_project_custom_fields: "Egyéni mezők engedélyezve" @@ -4132,6 +4130,7 @@ hu: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/id.yml b/config/locales/crowdin/id.yml index 7e71a58b3a0..f449f3081c0 100644 --- a/config/locales/crowdin/id.yml +++ b/config/locales/crowdin/id.yml @@ -1023,7 +1023,6 @@ id: title: "Alur kerja yang hilang untuk berbagi paket kerja" message: "Tidak ada alur kerja yang dikonfigurasikan untuk peran 'Editor paket kerja'. Tanpa alur kerja, pengguna yang dibagikan tidak dapat mengubah status paket kerja. Alur kerja dapat disalin. Pilih jenis sumber (misalnya 'Tugas') dan peran sumber (misalnya 'Anggota'). Kemudian pilih jenis target. Sebagai permulaan, Anda dapat memilih semua jenis sebagai target. Terakhir, pilih peran 'Editor paket kerja' sebagai target dan tekan 'Salin'. Setelah membuat default, sesuaikan alur kerja seperti yang Anda lakukan untuk setiap peran lainnya." link_message: "Mengonfigurasi alur kerja dalam administrasi." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3164,7 +3163,6 @@ id: label_duplicate: "duplicate" label_duplicates: "duplikat" label_edit: "Edit" - label_edit_attribute: "Edit attribute" label_edit_x: "Edit: %{x}" label_enable_multi_select: "Beralih ke multiselect" label_enabled_project_custom_fields: "Enabled custom fields" @@ -4078,6 +4076,7 @@ id: permission_edit_project_query: "Edit project query" placeholders: default: "Hapus nilai" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/it.yml b/config/locales/crowdin/it.yml index 3ad97cf0cdc..8deb6410a89 100644 --- a/config/locales/crowdin/it.yml +++ b/config/locales/crowdin/it.yml @@ -1033,7 +1033,6 @@ it: title: "Flusso di lavoro mancante per la condivisione di macro-attività" message: "Nessun flusso di lavoro è configurato per il ruolo \"Editor di macro-attività\". Senza un flusso di lavoro, la condivisione con l'utente non può alterare lo stato della macro-attività. I flussi di lavoro possono essere copiati. Seleziona un tipo di origine (ad esempio \"Attività\") e un ruolo di origine (ad esempio \"Membro\"). Quindi seleziona i tipi obiettivo. Per cominciare, potresti selezionare tutti i tipi come obiettivi. Infine, seleziona il ruolo \"Editor di macro-attività\" come destinazione e premi \"Copia\". Dopo aver creato le impostazioni predefinite, perfeziona i flussi di lavoro come fai per ogni altro ruolo." link_message: "Configura i flussi di lavoro nell'amministrazione." - templated_subject_hint: Generato automaticamente attraverso il tipo %{type} summary: reports: category: @@ -3214,7 +3213,6 @@ it: label_duplicate: "duplica" label_duplicates: "duplica" label_edit: "Modifica" - label_edit_attribute: "Modifica attributo" label_edit_x: "Modifica: %{x}" label_enable_multi_select: "Attiva/disattiva multiselezione" label_enabled_project_custom_fields: "Campi personalizzati abilitati" @@ -4131,6 +4129,7 @@ it: permission_edit_project_query: "Modifica elenco di progetti" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolio" diff --git a/config/locales/crowdin/ja.yml b/config/locales/crowdin/ja.yml index 80f3606b362..2ccee61a3ac 100644 --- a/config/locales/crowdin/ja.yml +++ b/config/locales/crowdin/ja.yml @@ -1026,7 +1026,6 @@ ja: title: "ワークパッケージの共有のためのワークフローがありません" message: "「ワークパッケージエディタ」ロールに対してワークフローが設定されていません。ワークフローがなければ、ユーザーと共有されたワークパッケージのステータスは変更できません。 ワークフローをコピーすることができます。ソースタイプ(例:「タスク」)とソースロール(例:「メンバー」)を選択します。 次に、ターゲットタイプを選択します。最初に、すべてのタイプをターゲットとして選択できます。 最後に、「ワークパッケージの編集」ロールをターゲットとして選択し、「コピー」を押します。 このようにしてデフォルトを作成した後、他のすべてのロールに対して行うようにワークフローを微調整します。" link_message: "管理画面でワークフローを構成します。" - templated_subject_hint: '%{type}タイプで自動生成されます' summary: reports: category: @@ -3167,7 +3166,6 @@ ja: label_duplicate: "重複" label_duplicates: "次と重複" label_edit: "編集" - label_edit_attribute: "Edit attribute" label_edit_x: "編集: %{x}" label_enable_multi_select: "複数選択の切り替え" label_enabled_project_custom_fields: "有効なカスタム フィールド" @@ -4082,6 +4080,7 @@ ja: permission_edit_project_query: "プロジェクトのクエリを編集" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/ka.yml b/config/locales/crowdin/ka.yml index fb4def25f90..8ae866907a9 100644 --- a/config/locales/crowdin/ka.yml +++ b/config/locales/crowdin/ka.yml @@ -1036,7 +1036,6 @@ ka: title: "Workflow missing for work package sharing" message: "No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role." link_message: "Configure the workflows in the administration." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3217,7 +3216,6 @@ ka: label_duplicate: "დუბლირება" label_duplicates: "დუბლიკატები" label_edit: "ჩასწორება" - label_edit_attribute: "Edit attribute" label_edit_x: "ჩასწორება: %{x}" label_enable_multi_select: "Toggle multiselect" label_enabled_project_custom_fields: "Enabled custom fields" @@ -4134,6 +4132,7 @@ ka: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/kk.yml b/config/locales/crowdin/kk.yml index 9618b73791a..bdb9c694fcd 100644 --- a/config/locales/crowdin/kk.yml +++ b/config/locales/crowdin/kk.yml @@ -1036,7 +1036,6 @@ kk: title: "Workflow missing for work package sharing" message: "No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role." link_message: "Configure the workflows in the administration." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3217,7 +3216,6 @@ kk: label_duplicate: "duplicate" label_duplicates: "duplicates" label_edit: "Edit" - label_edit_attribute: "Edit attribute" label_edit_x: "Edit: %{x}" label_enable_multi_select: "Toggle multiselect" label_enabled_project_custom_fields: "Enabled custom fields" @@ -4134,6 +4132,7 @@ kk: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/ko.yml b/config/locales/crowdin/ko.yml index ba653dce819..ad188a8fd50 100644 --- a/config/locales/crowdin/ko.yml +++ b/config/locales/crowdin/ko.yml @@ -1027,7 +1027,6 @@ ko: title: "작업 패키지 공유에 대한 워크플로 누락" message: "'작업 패키지 편집자' 역할에 대해 구성된 워크플로가 없습니다. 워크플로가 없으면 사용자와 공유된 워크플로는 작업 패키지의 상태를 변경할 수 없습니다. 워크플로를 복사할 수는 있습니다. 소스 유형(예: '작업')과 소스 역할(예: '멤버')을 선택하세요. 그런 다음 대상 유형을 선택하세요. 시작하기 위해 모든 유형을 대상으로 선택할 수 있습니다. 마지막으로 '작업 패키지 편집자' 역할을 대상으로 선택하고 '복사'를 누르세요. 기본값을 생성한 후 다른 모든 역할에 대해 수행하는 것처럼 워크플로를 미세 조정하세요." link_message: "관리에서 워크플로를 구성하세요." - templated_subject_hint: '%{type} 유형을 통해 자동으로 생성됨' summary: reports: category: @@ -3168,7 +3167,6 @@ ko: label_duplicate: "중복" label_duplicates: "복제" label_edit: "편집" - label_edit_attribute: "특성 편집" label_edit_x: "편집: %{x}" label_enable_multi_select: "다중 선택 토글" label_enabled_project_custom_fields: "사용자 정의 필드 사용" @@ -4082,6 +4080,7 @@ ko: permission_edit_project_query: "프로젝트 쿼리 편집" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0개 포트폴리오" diff --git a/config/locales/crowdin/lt.yml b/config/locales/crowdin/lt.yml index e221543b00b..3fb8c7c64e4 100644 --- a/config/locales/crowdin/lt.yml +++ b/config/locales/crowdin/lt.yml @@ -1051,7 +1051,6 @@ lt: title: "Darbo paketo dalinimuisi trūksta darbo proceso" message: "Vaidmeniui „Darbo paketo redaktorius“ nesukonfigūruotas procesas. Be proceso naudotojas negali pakeisti darbo paketo būsenos. Procesą galima nukopijuoti. Parinkite šaltinio tipą („pvz. „Užduotis“) ir šaltinio vaidmenį (pvz. „Narys“). Tada parinkite paskirties tipus. Pradžiai galėtumėte kaip paskirtį parinkti visus tipus. Galų gale parinkite vaidmenį „Darbo paketų redaktorius“ kaip paskirtį ir spauskite „Kopijuoti“. Taip sukūrę numatytąjį variantą, patikslinkite procesus kaip tai darote su kitais vaidmenimis." link_message: "Konfigūruokite procesus administravime." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3312,7 +3311,6 @@ lt: label_duplicate: "dubliuoti" label_duplicates: "dubliuojasi" label_edit: "Redaguoti" - label_edit_attribute: "Edit attribute" label_edit_x: "Redaguoti: %{x}" label_enable_multi_select: "Perjungti daugybinį pažymėjimą" label_enabled_project_custom_fields: "Įgalinti papildomi laukai" @@ -4232,6 +4230,7 @@ lt: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/lv.yml b/config/locales/crowdin/lv.yml index beb9ee014c7..830c6be356b 100644 --- a/config/locales/crowdin/lv.yml +++ b/config/locales/crowdin/lv.yml @@ -1045,7 +1045,6 @@ lv: title: "Workflow missing for work package sharing" message: "No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role." link_message: "Configure the workflows in the administration." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3266,7 +3265,6 @@ lv: label_duplicate: "duplicate" label_duplicates: "duplicates" label_edit: "Labot" - label_edit_attribute: "Edit attribute" label_edit_x: "Edit: %{x}" label_enable_multi_select: "Toggle multiselect" label_enabled_project_custom_fields: "Iespējotie pielāgotie lauki" @@ -4185,6 +4183,7 @@ lv: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/mn.yml b/config/locales/crowdin/mn.yml index 198b856ffb0..f6c2beea70a 100644 --- a/config/locales/crowdin/mn.yml +++ b/config/locales/crowdin/mn.yml @@ -1036,7 +1036,6 @@ mn: title: "Workflow missing for work package sharing" message: "No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role." link_message: "Configure the workflows in the administration." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3217,7 +3216,6 @@ mn: label_duplicate: "duplicate" label_duplicates: "duplicates" label_edit: "Edit" - label_edit_attribute: "Edit attribute" label_edit_x: "Edit: %{x}" label_enable_multi_select: "Toggle multiselect" label_enabled_project_custom_fields: "Enabled custom fields" @@ -4134,6 +4132,7 @@ mn: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/ms.yml b/config/locales/crowdin/ms.yml index dbcc526784d..365a5fc1c2d 100644 --- a/config/locales/crowdin/ms.yml +++ b/config/locales/crowdin/ms.yml @@ -1025,7 +1025,6 @@ ms: title: "Aliran kerja hilang untuk perkongsian pakej kerja" message: "Tiada aliran kerja yang dikonfigurasi untuk peranan 'Pengedit Pakej Kerja'. Tanpa aliran kerja, perkongsian dengan pengguna tidak boleh mengubah status pakej kerja tersebut. Aliran kerja boleh disalin. Pilih jenis sumber (cth. 'Tugasan') dan peranan sumber (cth. 'Ahli'). Kemudian pilih jenis sasaran. Sebagai permulaan, anda boleh pilih semua jenis sebagai sasaran. Akhirnya, pilih peranan 'Pengedit Pakej Kerja' sebagai sasaran dan tekan 'Salin'. Setelah mencipta default ini, selaraskan semula aliran kerja sebagaimana yang anda lakukan untuk setiap peranan yang lain." link_message: "Konfigurasi aliran kerja dalam pentadbiran." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3166,7 +3165,6 @@ ms: label_duplicate: "duplikasi" label_duplicates: "pendua" label_edit: "Edit" - label_edit_attribute: "Edit attribute" label_edit_x: "Edit: %{x}" label_enable_multi_select: "Tukar pilihan berganda" label_enabled_project_custom_fields: "Ruang tersuai yang diaktifkan" @@ -4080,6 +4078,7 @@ ms: permission_edit_project_query: "Edit pertanyaan projek" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/ne.yml b/config/locales/crowdin/ne.yml index 4044a3b43d5..050c2ab15ed 100644 --- a/config/locales/crowdin/ne.yml +++ b/config/locales/crowdin/ne.yml @@ -1036,7 +1036,6 @@ ne: title: "Workflow missing for work package sharing" message: "No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role." link_message: "Configure the workflows in the administration." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3217,7 +3216,6 @@ ne: label_duplicate: "duplicate" label_duplicates: "duplicates" label_edit: "Edit" - label_edit_attribute: "Edit attribute" label_edit_x: "Edit: %{x}" label_enable_multi_select: "Toggle multiselect" label_enabled_project_custom_fields: "Enabled custom fields" @@ -4134,6 +4132,7 @@ ne: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/nl.yml b/config/locales/crowdin/nl.yml index ebf15731969..1ddd33cf07b 100644 --- a/config/locales/crowdin/nl.yml +++ b/config/locales/crowdin/nl.yml @@ -1032,7 +1032,6 @@ nl: title: "Workflow missing for work package sharing" message: "No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role." link_message: "Configureer de workflows in de administratie." - templated_subject_hint: Automatisch gegenereerd via type %{type} summary: reports: category: @@ -3213,7 +3212,6 @@ nl: label_duplicate: "dupliceren" label_duplicates: "duplicaten" label_edit: "Wijzig" - label_edit_attribute: "Edit attribute" label_edit_x: "Bewerken: %{x}" label_enable_multi_select: "Omschakelen multiselect" label_enabled_project_custom_fields: "Ingeschakelde aangepaste velden" @@ -4129,6 +4127,7 @@ nl: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/no.yml b/config/locales/crowdin/no.yml index 079c55421ce..ca05a217bcf 100644 --- a/config/locales/crowdin/no.yml +++ b/config/locales/crowdin/no.yml @@ -1035,7 +1035,6 @@ title: "Arbeidsflyt mangler for deling av arbeidspakker" message: "Ingen arbeidsflyt er konfigurert for rollen 'Arbeidspakke redaktør'. Uten en arbeidsflyt kan ikke brukeren endre status på arbeidspakken. Arbeidsflyt kan kopieres. Velg en kildetype (f.eks. 'oppgave') og en kilderolle (f.eks. 'medlem'). Velg så måltyper. Til å begynne med, kan du velge alle typene som mål. Til slutt velger du \"Redigeringsprogrammet for arbeidspakke\" som mål og press \"Copy\". Etter å ha opprettet standardinnstillingene, kan du finjustere arbeidsflytene som du gjør for hver annen rolle." link_message: "Konfigurer arbeidsmetodene i administrasjonen." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3216,7 +3215,6 @@ label_duplicate: "duplikat" label_duplicates: "duplikater" label_edit: "Rediger" - label_edit_attribute: "Edit attribute" label_edit_x: "Rediger: %{x}" label_enable_multi_select: "Veksle multivalg" label_enabled_project_custom_fields: "Aktiverte egendefinerte felt" @@ -4133,6 +4131,7 @@ permission_edit_project_query: "Rediger prosjektspørring" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/pl.yml b/config/locales/crowdin/pl.yml index 1ec15758fb8..a20acc9b6db 100644 --- a/config/locales/crowdin/pl.yml +++ b/config/locales/crowdin/pl.yml @@ -1051,7 +1051,6 @@ pl: title: "Brak przepływu pracy udostępniania pakietu roboczego" message: "Dla roli „Edytor pakietów roboczych” nie skonfigurowano przepływu pracy. Bez przepływu pracy użytkownik, któremu ją udostępniono nie może zmienić statusu pakietu roboczego. Przepływy pracy można kopiować. Wybierz typ źródłowy (np. „Zadanie”) i rolę źródłową (np. „Członek”). Następnie wybierz typy docelowe. Na początek możesz wybrać wszystkie typy jako docelowe. Na koniec wybierz rolę „Edytor pakietów roboczych” jako cel i naciśnij przycisk „Kopiuj”. Po utworzeniu ustawień domyślnych dostosuj przepływy pracy tak, jak w przypadku każdej innej roli." link_message: "Skonfiguruj przepływy pracy w administracji." - templated_subject_hint: Automatycznie wygenerowany przez typ %{type} summary: reports: category: @@ -3311,7 +3310,6 @@ pl: label_duplicate: "duplikat" label_duplicates: "Duplikaty" label_edit: "Edytuj" - label_edit_attribute: "Edytuj atrybut" label_edit_x: "Edytuj: %{x}" label_enable_multi_select: "Włącz wybór wielokrotny" label_enabled_project_custom_fields: "Aktywne pola niestandardowe" @@ -4231,6 +4229,7 @@ pl: permission_edit_project_query: "Edytuj zapytanie dotyczące projektu" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfoliów" diff --git a/config/locales/crowdin/pt-BR.yml b/config/locales/crowdin/pt-BR.yml index 8c81dcabe28..8aa3058b812 100644 --- a/config/locales/crowdin/pt-BR.yml +++ b/config/locales/crowdin/pt-BR.yml @@ -1033,7 +1033,6 @@ pt-BR: title: "Fluxo de trabalho ausente para compartilhamento de pacotes de trabalho" message: "Nenhum fluxo de trabalho está configurado para a função \"Editor de pacote de trabalho\". Sem um fluxo de trabalho, o usuário compartilhado não pode alterar o status do pacote de trabalho. Os fluxos de trabalho podem ser copiados. Selecione um tipo de origem (ex.: \"Tarefa\") e uma função de origem (ex.: \"Membro\"). Em seguida, selecione os tipos de destino. Para começar, você pode selecionar todos os tipos como alvos. Por fim, selecione a função \"Editor de pacote de trabalho\" como o destino e pressione \"Copiar\". Depois de criar os padrões, ajuste os fluxos de trabalho da mesma forma que faz para todas as outras funções." link_message: "Configure os fluxos de trabalho na administração." - templated_subject_hint: Gerado automaticamente através do tipo %{type} summary: reports: category: @@ -3214,7 +3213,6 @@ pt-BR: label_duplicate: "duplicado" label_duplicates: "Duplicados" label_edit: "Editar" - label_edit_attribute: "Editar atributo" label_edit_x: "Editar: %{x}" label_enable_multi_select: "Alterna para seleção múltipla" label_enabled_project_custom_fields: "Campos personalizados habilitados" @@ -4130,6 +4128,7 @@ pt-BR: permission_edit_project_query: "Editar consulta do projeto" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfólios" diff --git a/config/locales/crowdin/pt-PT.yml b/config/locales/crowdin/pt-PT.yml index 6dccc8089d5..bd68610219f 100644 --- a/config/locales/crowdin/pt-PT.yml +++ b/config/locales/crowdin/pt-PT.yml @@ -1033,7 +1033,6 @@ pt-PT: title: "Falta um fluxo de trabalho para a partilha de pacotes de trabalho" message: "Nenhum fluxo de trabalho está configurado para a função \"Editor do pacote de trabalho\". Sem um fluxo de trabalho, o utilizador não pode alterar o estado do pacote de trabalho. Os fluxos de trabalho podem ser copiados. Selecione um tipo de fonte (por exemplo, \"Tarefa\") e uma função da fonte (por exemplo, \"Membro\"). Em seguida, selecione os tipos de destino. Para começar, pode selecionar todos os tipos como alvos. Por fim, selecione a função \"Editor do pacote de trabalho\" como destino e prima \"Copiar\". Depois de ter criado as predefinições, ajuste os fluxos de trabalho como faz para todas as outras funções." link_message: "Configure os fluxos de trabalho na administração." - templated_subject_hint: Gerado automaticamente através do tipo %{type} summary: reports: category: @@ -3214,7 +3213,6 @@ pt-PT: label_duplicate: "duplicado" label_duplicates: "duplicados" label_edit: "Editar" - label_edit_attribute: "Editar atributo" label_edit_x: "Editar: %{x}" label_enable_multi_select: "Alternar para selecção múltipla" label_enabled_project_custom_fields: "Campos personalizados activados" @@ -4130,6 +4128,7 @@ pt-PT: permission_edit_project_query: "Editar consulta de projeto" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 carteiras" diff --git a/config/locales/crowdin/ro.yml b/config/locales/crowdin/ro.yml index ab4d512fd63..711dab88215 100644 --- a/config/locales/crowdin/ro.yml +++ b/config/locales/crowdin/ro.yml @@ -1045,7 +1045,6 @@ ro: title: "Lipsește fluxul pentru partajarea pachetului de lucru" message: "Niciun flux de lucru nu este configurat pentru rolul 'Editor pachete de lucru'. Fără un flux de lucru, utilizatorul nu poate modifica statusul pachetului de lucru. Fluxurile de lucru pot fi copiate. Selectează un tip sursă (de ex. 'Task') şi rolul sursă (de ex. 'Membru'). Apoi selectează tipurile țintă. Pentru a începe, ai putea selecta toate tipurile ca ținte. După aceea, selectează rolul de „editor pachete de lucru” ca țintă și apăsă „Copiază”. După ce ai creat cele implicite, reglează fluxurile de lucru așa cum faci pentru orice alt rol." link_message: "Configurează fluxurile de lucru din administrare." - templated_subject_hint: Generat automat prin tipul %{type} summary: reports: category: @@ -3266,7 +3265,6 @@ ro: label_duplicate: "duplicat" label_duplicates: "dublează" label_edit: "Editare" - label_edit_attribute: "Edit attribute" label_edit_x: "Editare: %{x}" label_enable_multi_select: "Comutare selecție multiplă" label_enabled_project_custom_fields: "Câmpuri personalizate activate" @@ -4184,6 +4182,7 @@ ro: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/ru.yml b/config/locales/crowdin/ru.yml index e81c2224c2d..cd9d776b3b1 100644 --- a/config/locales/crowdin/ru.yml +++ b/config/locales/crowdin/ru.yml @@ -1052,7 +1052,6 @@ ru: title: "Отсутствует рабочий процесс для совместного использования пакета работ" message: "Рабочий процесс не настроен для роли 'Редактора пакетов работ'. Без рабочего процесса, общий с пользователем не может изменить статус рабочего пакета. Рабочие процессы могут быть скопированы. Выберите исходный тип (например, «Задача») и исходную роль (например, «Участник»). Затем выберите нужные типы. Чтобы начать, можно выбрать все типы в качестве целей. Наконец, выберите роль 'Редактор пакетов работ' в качестве цели и нажмите 'Копировать'. После создания таким образом по умолчанию, тонкая настройка рабочих процессов как вы делаете для каждой другой роли." link_message: "Настройка рабочих процессов в администрации." - templated_subject_hint: Автоматически сгенерировано по типу %{type} summary: reports: category: @@ -3313,7 +3312,6 @@ ru: label_duplicate: "дублировать" label_duplicates: "Дублирует" label_edit: "Правка" - label_edit_attribute: "Изменить атрибут" label_edit_x: "Правка: %{x}" label_enable_multi_select: "Разрешен множественный выбор" label_enabled_project_custom_fields: "Доступные настраиваемые поля" @@ -4233,6 +4231,7 @@ ru: permission_edit_project_query: "Редактирование запроса проекта" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 портфолио" diff --git a/config/locales/crowdin/rw.yml b/config/locales/crowdin/rw.yml index 44f8134f3aa..c2bda652670 100644 --- a/config/locales/crowdin/rw.yml +++ b/config/locales/crowdin/rw.yml @@ -1036,7 +1036,6 @@ rw: title: "Workflow missing for work package sharing" message: "No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role." link_message: "Configure the workflows in the administration." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3217,7 +3216,6 @@ rw: label_duplicate: "duplicate" label_duplicates: "duplicates" label_edit: "Edit" - label_edit_attribute: "Edit attribute" label_edit_x: "Edit: %{x}" label_enable_multi_select: "Toggle multiselect" label_enabled_project_custom_fields: "Enabled custom fields" @@ -4134,6 +4132,7 @@ rw: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/si.yml b/config/locales/crowdin/si.yml index e96ba91d87a..380f8b5d5db 100644 --- a/config/locales/crowdin/si.yml +++ b/config/locales/crowdin/si.yml @@ -1036,7 +1036,6 @@ si: title: "Workflow missing for work package sharing" message: "No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role." link_message: "Configure the workflows in the administration." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3217,7 +3216,6 @@ si: label_duplicate: "අනුපිටපත්" label_duplicates: "අනුපිටපත්" label_edit: "සංස්කරණය කරන්න" - label_edit_attribute: "Edit attribute" label_edit_x: "Edit: %{x}" label_enable_multi_select: "බහු ටොගල් කරන්න" label_enabled_project_custom_fields: "සක්රීය අභිරුචි ක්ෂේත්ර" @@ -4134,6 +4132,7 @@ si: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/sk.yml b/config/locales/crowdin/sk.yml index a5bfa01c932..e4180b27d77 100644 --- a/config/locales/crowdin/sk.yml +++ b/config/locales/crowdin/sk.yml @@ -1054,7 +1054,6 @@ sk: title: "Workflow missing for work package sharing" message: "No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role." link_message: "Configure the workflows in the administration." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3315,7 +3314,6 @@ sk: label_duplicate: "skopírovať" label_duplicates: "duplikáty" label_edit: "Upraviť" - label_edit_attribute: "Edit attribute" label_edit_x: "Edit: %{x}" label_enable_multi_select: "Prepnúť multiselect" label_enabled_project_custom_fields: "Povolené vlastné polia" @@ -4235,6 +4233,7 @@ sk: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/sl.yml b/config/locales/crowdin/sl.yml index af19b7b697b..e51d5157b6f 100644 --- a/config/locales/crowdin/sl.yml +++ b/config/locales/crowdin/sl.yml @@ -1053,7 +1053,6 @@ sl: title: "Workflow missing for work package sharing" message: "No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role." link_message: "Configure the workflows in the administration." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3314,7 +3313,6 @@ sl: label_duplicate: "Podvoji" label_duplicates: "dvojniki" label_edit: "Uredi" - label_edit_attribute: "Edit attribute" label_edit_x: "Uredi: %{x}" label_enable_multi_select: "Preklopite na več selekcijo" label_enabled_project_custom_fields: "Omogočena polja po meri" @@ -4235,6 +4233,7 @@ sl: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/sr.yml b/config/locales/crowdin/sr.yml index a2372bfc75b..eb2e9131476 100644 --- a/config/locales/crowdin/sr.yml +++ b/config/locales/crowdin/sr.yml @@ -1045,7 +1045,6 @@ sr: title: "Workflow missing for work package sharing" message: "No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role." link_message: "Configure the workflows in the administration." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3266,7 +3265,6 @@ sr: label_duplicate: "duplicate" label_duplicates: "duplicates" label_edit: "Edit" - label_edit_attribute: "Edit attribute" label_edit_x: "Edit: %{x}" label_enable_multi_select: "Toggle multiselect" label_enabled_project_custom_fields: "Enabled custom fields" @@ -4185,6 +4183,7 @@ sr: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/sv.yml b/config/locales/crowdin/sv.yml index d95490614a8..124024b1171 100644 --- a/config/locales/crowdin/sv.yml +++ b/config/locales/crowdin/sv.yml @@ -1036,7 +1036,6 @@ sv: title: "Arbetsflöde saknas för delning av arbetspaket" message: "Inget arbetsflöde är konfigurerat för 'Work package editor'-rollen. Utan ett arbetsflöde kan den delade användaren inte ändra arbetspaketets status. Arbetsflöden kan kopieras. Välj en källtyp (t.ex. 'Task') och källroll (t.ex. 'Medlem'). Välj sedan måltyper. Till att börja med kan du välja alla typer som mål. Slutligen väljer du rollen \"Arbetspaket editor\" som mål och trycker på \"Kopiera\". Efter att ha skapat standardinställningarna, finjustera arbetsflödena som du gör för varje annan roll." link_message: "Konfigurera arbetsflödena i administrationen." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3217,7 +3216,6 @@ sv: label_duplicate: "dublett" label_duplicates: "dubblett av" label_edit: "Redigera" - label_edit_attribute: "Edit attribute" label_edit_x: "Redigera: %{x}" label_enable_multi_select: "Växla multival" label_enabled_project_custom_fields: "Aktiverade anpassade fält" @@ -4134,6 +4132,7 @@ sv: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/th.yml b/config/locales/crowdin/th.yml index 3535c0a47f4..1f3b1999893 100644 --- a/config/locales/crowdin/th.yml +++ b/config/locales/crowdin/th.yml @@ -1027,7 +1027,6 @@ th: title: "Workflow missing for work package sharing" message: "No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role." link_message: "Configure the workflows in the administration." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3168,7 +3167,6 @@ th: label_duplicate: "ทำซ้ำ" label_duplicates: "ซ้ำ" label_edit: "แก้ไข" - label_edit_attribute: "Edit attribute" label_edit_x: "Edit: %{x}" label_enable_multi_select: "สลับไปเลือกหลายค่า" label_enabled_project_custom_fields: "Enabled custom fields" @@ -4083,6 +4081,7 @@ th: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/tr.yml b/config/locales/crowdin/tr.yml index cbed0c5f959..0c2fb270d5d 100644 --- a/config/locales/crowdin/tr.yml +++ b/config/locales/crowdin/tr.yml @@ -1036,7 +1036,6 @@ tr: title: "İş paketi paylaşımı için iş akışı eksik" message: "'İş paketi düzenleyicisi' rolü için hiçbir iş akışı yapılandırılmamıştır. Bir iş akışı olmadan, paylaşılan kullanıcı iş paketinin durumunu değiştiremez. İş akışları kopyalanabilir. Bir kaynak türü (örn. 'Görev') ve kaynak rolü (örn. 'Üye') seçin. Ardından hedef türleri seçin. Başlangıç olarak, tüm türleri hedef olarak seçebilirsiniz. Son olarak, hedef olarak 'İş paketi düzenleyicisi' rolünü seçin ve 'Kopyala' düğmesine basın. Varsayılanları bu şekilde oluşturduktan sonra, diğer tüm roller için yaptığınız gibi iş akışlarında ince ayar yapın." link_message: "İş akışlarını yönetim alanından yapılandırın." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3217,7 +3216,6 @@ tr: label_duplicate: "kopya" label_duplicates: "kopyalayan" label_edit: "Düzenle" - label_edit_attribute: "Edit attribute" label_edit_x: "Düzenle: %{x}" label_enable_multi_select: "Çoklu seçimi etkinleştir" label_enabled_project_custom_fields: "Etkin özel alanlar" @@ -4132,6 +4130,7 @@ tr: permission_edit_project_query: "Proje sorgusunu düzenleme" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/uk.yml b/config/locales/crowdin/uk.yml index f269844d9ab..a7af565c7c8 100644 --- a/config/locales/crowdin/uk.yml +++ b/config/locales/crowdin/uk.yml @@ -1049,7 +1049,6 @@ uk: title: "Відсутній робочий процес для надання доступу до пакета робіт" message: "Для ролі «Редактор пакета робіт» не налаштовано жодного робочого процесу. Без робочого процесу користувач, якому надано доступ, не може змінити статус пакета робіт. Робочі процеси можна копіювати. Виберіть вихідний тип (напр., «Завдання») і роль (напр., «Учасник»). Потім виберіть цільові типи. Для початку радимо вибирати всі типи як цільові. Нарешті, виберіть роль «Редактор пакета робіт» і натисніть «Копіювати». Після цього ви зможете налаштовувати робочі процеси для кожної ролі, як ви це зазвичай робите." link_message: "Налаштуйте робочі процеси на панелі адміністрування." - templated_subject_hint: Автоматично згенеровано з використанням типу %{type} summary: reports: category: @@ -3309,7 +3308,6 @@ uk: label_duplicate: "Дублювати" label_duplicates: "Дублікати" label_edit: "Редагувати" - label_edit_attribute: "Редагувати атрибут" label_edit_x: "Редагувати: %{x}" label_enable_multi_select: "Перемкнути мультиселекцію" label_enabled_project_custom_fields: "Увімкнено спеціальні поля" @@ -4228,6 +4226,7 @@ uk: permission_edit_project_query: "Редагування запиту проєктів" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 портфелів" diff --git a/config/locales/crowdin/uz.yml b/config/locales/crowdin/uz.yml index 05311a3730e..26cb20c36ba 100644 --- a/config/locales/crowdin/uz.yml +++ b/config/locales/crowdin/uz.yml @@ -1036,7 +1036,6 @@ uz: title: "Workflow missing for work package sharing" message: "No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role." link_message: "Configure the workflows in the administration." - templated_subject_hint: Automatically generated through type %{type} summary: reports: category: @@ -3217,7 +3216,6 @@ uz: label_duplicate: "duplicate" label_duplicates: "duplicates" label_edit: "Edit" - label_edit_attribute: "Edit attribute" label_edit_x: "Edit: %{x}" label_enable_multi_select: "Toggle multiselect" label_enabled_project_custom_fields: "Enabled custom fields" @@ -4134,6 +4132,7 @@ uz: permission_edit_project_query: "Edit project query" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 portfolios" diff --git a/config/locales/crowdin/vi.yml b/config/locales/crowdin/vi.yml index bef9ae26544..859f0c83c2f 100644 --- a/config/locales/crowdin/vi.yml +++ b/config/locales/crowdin/vi.yml @@ -1025,7 +1025,6 @@ vi: title: "Thiếu luồng công việc để chia sẻ gói công việc" message: "Không có quy trình làm việc nào được định cấu hình cho vai trò 'Trình chỉnh sửa gói công việc'. Nếu không có quy trình làm việc, nội dung được chia sẻ với người dùng không thể thay đổi trạng thái của gói công việc. Quy trình làm việc có thể được sao chép. Chọn loại nguồn (ví dụ: 'Nhiệm vụ') và vai trò nguồn (ví dụ: 'Thành viên'). Sau đó chọn loại mục tiêu. Để bắt đầu, bạn có thể chọn tất cả các loại làm mục tiêu. Cuối cùng, chọn vai trò 'Trình chỉnh sửa gói công việc' làm mục tiêu và nhấn 'Sao chép'. Sau khi đã tạo các giá trị mặc định, hãy tinh chỉnh quy trình làm việc như bạn thực hiện với mọi vai trò khác." link_message: "Cấu hình các quy trình công việc trong quản trị." - templated_subject_hint: Được tạo tự động thông qua loại %{type} summary: reports: category: @@ -3166,7 +3165,6 @@ vi: label_duplicate: "Nhân đôi" label_duplicates: "Nhân đôi" label_edit: "Chỉnh sửa" - label_edit_attribute: "Chỉnh sửa thuộc tính" label_edit_x: "Chỉnh sửa: %{x}" label_enable_multi_select: "Bật/tắt đa lựa chọn" label_enabled_project_custom_fields: "Các trường tùy chỉnh đã bật" @@ -4081,6 +4079,7 @@ vi: permission_edit_project_query: "Chỉnh sửa truy vấn dự án" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 danh mục đầu tư" diff --git a/config/locales/crowdin/zh-CN.yml b/config/locales/crowdin/zh-CN.yml index 918d9eb3148..b500be5d5ec 100644 --- a/config/locales/crowdin/zh-CN.yml +++ b/config/locales/crowdin/zh-CN.yml @@ -1023,7 +1023,6 @@ zh-CN: title: "工作包共享缺少工作流" message: "没有为\"工作包编辑者\"角色配置工作流。没有工作流,共享用户就无法更改工作包的状态。工作流可以复制。选择一个源类型(例如\"任务\")和源角色(例如\"成员\")。然后选择目标类型。一开始,您可以将所有类型都选择为目标类型。最后,选择\"工作包编辑者\"角色作为目标,然后点击\"复制\"。在创建默认设置之后,像对其他角色一样进行微调,对工作流进行详细调整。" link_message: "在管理中配置工作流。" - templated_subject_hint: 通过类型 %{type}自动生成 summary: reports: category: @@ -3164,7 +3163,6 @@ zh-CN: label_duplicate: "重复" label_duplicates: "复制" label_edit: "编辑" - label_edit_attribute: "编辑属性" label_edit_x: "编辑:%{x}" label_enable_multi_select: "切换多选" label_enabled_project_custom_fields: "启用自定义字段" @@ -4076,6 +4074,7 @@ zh-CN: permission_edit_project_query: "编辑项目查询" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 个项目组合" diff --git a/config/locales/crowdin/zh-TW.yml b/config/locales/crowdin/zh-TW.yml index 92d8d5a3536..11fcd9b9091 100644 --- a/config/locales/crowdin/zh-TW.yml +++ b/config/locales/crowdin/zh-TW.yml @@ -1024,7 +1024,6 @@ zh-TW: title: "共用工作套件缺少工作流" message: "「工作套件編輯者」角色尚未設定工作流程。沒有工作流程時,共享給該角色的使用者無法變更工作套件的狀態。您可以複製其他工作流程:選擇來源類型(例如「任務」)和來源角色(例如「成員」)。接著選擇目標類型。初期建議選擇所有類型作為目標。最後,選擇「工作套件編輯者」角色作為目標,並按下「複製」。建立預設後,您可以像調整其他角色的工作流程一樣,進行細部調整。" link_message: "在管理中配置工作流。" - templated_subject_hint: 透過類型 %{type}自動產生 summary: reports: category: @@ -3164,7 +3163,6 @@ zh-TW: label_duplicate: "重複" label_duplicates: "重複" label_edit: "編輯" - label_edit_attribute: "編輯屬性" label_edit_x: "編輯:%{x}" label_enable_multi_select: "取用複選" label_enabled_project_custom_fields: "開啟客製欄位" @@ -4077,6 +4075,7 @@ zh-TW: permission_edit_project_query: "編輯專案查詢" placeholders: default: "-" + templated_hint: Automatically generated through type %{type} portfolio: count: zero: "0 個組合" diff --git a/modules/meeting/config/locales/crowdin/de.yml b/modules/meeting/config/locales/crowdin/de.yml index 0d4f4111385..cbd731dad04 100644 --- a/modules/meeting/config/locales/crowdin/de.yml +++ b/modules/meeting/config/locales/crowdin/de.yml @@ -66,9 +66,9 @@ de: errors: models: meeting_participant: - user_invalid: "is not a valid participant." + user_invalid: "ist kein gültiger Teilnehmer." meeting_agenda_item: - user_invalid: "is not a valid participant." + user_invalid: "ist kein gültiger Teilnehmer." recurring_meeting: must_cover_existing_meetings: one: "Es gibt eine offene Besprechung in der Terminserie, die nicht durch den neuen Zeitplan abgedeckt ist. Passen Sie den Zeitplan an, um alle bestehenden Meetings einzuschließen." @@ -233,9 +233,9 @@ de: header: "Abgesagt: Besprechung '%{title}'" header_occurrence: "Abgesagt: Wiederkehrende Besprechung '%{title}'" header_series: "Abgesagt: Terminserie '%{title}'" - summary_occurrence: "An occurrence of '%{title}' has been cancelled by %{actor}, or you have been removed as a participant" - summary_series: "Meeting series '%{title}' has been cancelled by %{actor}, or you have been removed as a participant" - summary: "'%{title}' has been cancelled by %{actor}, or you have been removed as a participant" + summary_occurrence: "Eine Besprechung der Terminserie '%{title}' wurde von %{actor} abgesagt, oder Sie wurden als Teilnehmer entfernt" + summary_series: "Terminserie '%{title}' wurde von %{actor} gelöscht, oder Sie wurden als Teilnehmer entfernt" + summary: "'%{title}' wurde von %{actor} abgesagt, oder Sie wurden als Teilnehmer entfernt" date_time: "Geplanter Zeitpunkt" participant_added: header: "Besprechung '%{title}' - Teilnehmer hinzugefügt" @@ -249,7 +249,7 @@ de: summary_series: "%{actor} hat %{participant} aus Terminserie '%{title}' entfernt" ended: header_series: "Beendet: Terminserie '%{title}'" - summary_series: "Meeting series '%{title}' has been ended by %{actor}" + summary_series: "Die Terminserie '%{title}' wurde von %{actor} beendet" updated: header: "Besprechung '%{title}' wurde aktualisiert" summary: "Die Besprechung '%{title}' wurde durch %{actor} aktualisiert" @@ -532,8 +532,8 @@ de: label_agenda_item_move_up: "Nach oben verschieben" label_agenda_item_move_down: "Nach unten verschieben" label_agenda_item_duplicate: "Duplizieren" - label_agenda_item_duplicate_in_next: "Duplicate in next meeting" - label_agenda_item_duplicate_in_next_title: "Duplicate in next meeting?" + label_agenda_item_duplicate_in_next: "In die nächste Besprechung duplizieren" + label_agenda_item_duplicate_in_next_title: "In die nächste Besprechung duplizieren?" label_agenda_item_add_notes: "Notiz hinzufügen" label_agenda_item_add_outcome: "Ergebnis hinzufügen" label_agenda_item_work_package_add: "Arbeitspaket hinzufügen" @@ -613,9 +613,9 @@ de: text_agenda_item_duplicate_in_next_meeting: "Sind Sie sicher, dass Sie eine Kopie dieses Tagesordnungspunktes in die nächste Besprechung am %{date} um %{time} aufnehmen wollen? Ergebnisse dieses Eintrags werden nicht kopiert." text_agenda_item_duplicated_in_next_meeting: "Tagesordnungspunkt zum nächsten Besprechung am %{date} kopiert" text_work_package_has_no_upcoming_meeting_agenda_items: "Dieses Arbeitspaket ist bisher in keiner anstehenden Besprechung enthalten." - text_agenda_item_no_available_occurrence: "All upcoming occurrences have been cancelled." - text_agenda_item_dialog_skipping_cancelled_one: "Note: Skipping cancelled occurrence on %{date}." - text_agenda_item_dialog_skipping_cancelled_many: "Note: Skipping %{count} cancelled occurrences." + text_agenda_item_no_available_occurrence: "Alle kommenden Besprechungen wurden abgesagt." + text_agenda_item_dialog_skipping_cancelled_one: "Hinweis: Abgesagte Besprechung am %{date} wird übersprungen." + text_agenda_item_dialog_skipping_cancelled_many: "Hinweis: Überspringe %{count} abgesagte Besprechungen." text_work_package_add_to_meeting_hint: 'Über den Button "Zur Besprechung hinzufügen" können Sie dieses Arbeitspaket zu einer zukünftigen Besprechung hinzuzufügen.' text_work_package_has_no_past_meeting_agenda_items: "Dieses Arbeitspaket wurde in einer früheren Besprechung nicht als Tagesordnungspunkt hinzugefügt." text_email_updates_muted: "E-Mail-Kalenderaktualisierungen sind deaktiviert. Die Teilnehmer erhalten keine aktualisierten Einladungen per E-Mail, wenn Sie Änderungen vornehmen." diff --git a/modules/storages/config/locales/crowdin/de.yml b/modules/storages/config/locales/crowdin/de.yml index 5bfe349351d..7abfc15b703 100644 --- a/modules/storages/config/locales/crowdin/de.yml +++ b/modules/storages/config/locales/crowdin/de.yml @@ -104,20 +104,20 @@ de: create_folder: 'Verwaltete Projekt-Ordnererstellung:' ensure_root_folder_permissions: 'Basisordner-Berechtigungen festlegen:' hide_inactive_folders: 'Verstecke Inaktive Ordner:' - remote_folders: 'Read contents of the team folder:' + remote_folders: 'Inhalt des Teamordners lesen:' remove_user_from_group: 'Benutzer aus Gruppe entfernen:' rename_project_folder: 'Verwalteten Projektordner umbenennen:' one_drive_sync_service: create_folder: 'Verwaltete Projekt-Ordnererstellung:' ensure_root_folder_permissions: 'Basisordner-Berechtigungen festlegen:' hide_inactive_folders: 'Verstecke Inaktive Ordner:' - remote_folders: 'Read contents of the drive root folder:' + remote_folders: 'Inhalt des Root-Ordners des Laufwerks lesen:' rename_project_folder: 'Verwalteten Projektordner umbenennen:' sharepoint_sync_service: create_folder: 'Verwaltete Projekt-Ordnererstellung:' ensure_root_folder_permissions: 'Basisordner-Berechtigungen festlegen:' hide_inactive_folders: 'Verstecke Inaktive Ordner:' - remote_folders: 'Read contents of the drive root folder:' + remote_folders: 'Inhalt des Root-Ordners des Laufwerks lesen:' rename_project_folder: 'Verwalteten Projektordner umbenennen:' errors: messages: @@ -140,7 +140,7 @@ de: conflict: Der Ordner %{folder_name} existiert bereits in %{parent_location}. not_found: "%{parent_location} wurde nicht gefunden." ensure_root_folder_permissions: - not_found: "%{group_folder} wasn't found. Please check your Nextcloud Team Folder setup." + not_found: "%{group_folder} wurde nicht gefunden. Bitte überprüfen Sie Ihr Nextcloud Teamordner-Setup." permission_not_set: konnte keine Berechtigungen auf %{group_folder} setzen. hide_inactive_folders: permission_not_set: konnte keine Berechtigungen auf %{path} setzen. @@ -230,7 +230,7 @@ de: storage_delete_result_3: Der automatisch verwaltete Projektordner und alle darin enthaltenen Dateien werden gelöscht dependencies: nextcloud: - group_folders_app: Team Folders + group_folders_app: Teamordner integration_app: OpenProject Integration enabled_in_projects: setup_incomplete_description: Dieser Speicher ist nicht vollständig eingerichtet. Bitte schließen Sie die Einrichtung ab, bevor Sie ihn in mehreren Projekten aktivieren. @@ -277,11 +277,11 @@ de: client_folder_creation: Automatische Ordnererstellung client_folder_removal: Automatische Ordnerlöschung drive_contents: Speicherinhalt - files_request: Fetching team folder files + files_request: Teamordnerdateien werden abgerufen header: Automatisch verwaltete Projektordner - team_folder_app: 'Dependency: Team Folders' - team_folder_contents: Team folder content - team_folder_presence: Team folder exists + team_folder_app: 'Abhängigkeit: Teamordner' + team_folder_contents: Inhalt des Teamordners + team_folder_presence: Teamordner existiert userless_access: Serverseitige Anfrageauthentifizierung authentication: existing_token: Benutzer Token @@ -322,8 +322,8 @@ de: nc_oauth_request_not_found: Der Endpunkt für den Abruf des aktuell verbundenen Benutzers wurde nicht gefunden. Bitte überprüfen Sie die Serverprotokolle für weitere Informationen. nc_oauth_request_unauthorized: Der aktuelle Benutzer ist nicht berechtigt, auf den Remote-Datei-Speicher zuzugreifen. Bitte überprüfen Sie die Server-Protokolle für weitere Informationen. nc_oauth_token_missing: OpenProject kann die Kommunikation auf Benutzerebene mit Nextcloud nicht testen, da der Benutzer sein Nextcloud Konto noch nicht verknüpft hat. - nc_team_folder_not_found: The team folder could not be found. - nc_unexpected_content: Unexpected content found in the managed team folder. + nc_team_folder_not_found: Der Teamordner konnte nicht gefunden werden. + nc_unexpected_content: Unerwarteter Inhalt im verwalteten Teamordner gefunden. nc_userless_access_denied: Das konfigurierte App-Passwort ist ungültig. not_configured: Die Verbindung konnte nicht validiert werden. Bitte schließen Sie zuerst die Konfiguration ab. od_client_cant_delete_folder: Der Client hat Probleme beim Löschen von Ordnern. Bitte überprüfen Sie die Setup-Dokumentation für Ihren Speicher. From e196f6eadb41552d17a56a76f64779f00bd70c0c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 05:37:54 +0000 Subject: [PATCH 244/293] Bump the angular group in /frontend with 14 updates Bumps the angular group in /frontend with 14 updates: | Package | From | To | | --- | --- | --- | | [@angular/animations](https://github.com/angular/angular/tree/HEAD/packages/animations) | `21.1.2` | `21.1.3` | | [@angular/cdk](https://github.com/angular/components) | `21.1.2` | `21.1.3` | | [@angular/cli](https://github.com/angular/angular-cli) | `21.1.2` | `21.1.3` | | [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `21.1.2` | `21.1.3` | | [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) | `21.1.2` | `21.1.3` | | [@angular/compiler-cli](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli) | `21.1.2` | `21.1.3` | | [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `21.1.2` | `21.1.3` | | [@angular/elements](https://github.com/angular/angular/tree/HEAD/packages/elements) | `21.1.2` | `21.1.3` | | [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `21.1.2` | `21.1.3` | | [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `21.1.2` | `21.1.3` | | [@angular/platform-browser-dynamic](https://github.com/angular/angular/tree/HEAD/packages/platform-browser-dynamic) | `21.1.2` | `21.1.3` | | [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `21.1.2` | `21.1.3` | | [@angular-devkit/build-angular](https://github.com/angular/angular-cli) | `21.1.2` | `21.1.3` | | [@angular/language-service](https://github.com/angular/angular/tree/HEAD/packages/language-service) | `21.1.2` | `21.1.3` | Updates `@angular/animations` from 21.1.2 to 21.1.3 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.1.3/packages/animations) Updates `@angular/cdk` from 21.1.2 to 21.1.3 - [Release notes](https://github.com/angular/components/releases) - [Changelog](https://github.com/angular/components/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/components/compare/v21.1.2...v21.1.3) Updates `@angular/cli` from 21.1.2 to 21.1.3 - [Release notes](https://github.com/angular/angular-cli/releases) - [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular-cli/compare/v21.1.2...v21.1.3) Updates `@angular/common` from 21.1.2 to 21.1.3 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.1.3/packages/common) Updates `@angular/compiler` from 21.1.2 to 21.1.3 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.1.3/packages/compiler) Updates `@angular/compiler-cli` from 21.1.2 to 21.1.3 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.1.3/packages/compiler-cli) Updates `@angular/core` from 21.1.2 to 21.1.3 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.1.3/packages/core) Updates `@angular/elements` from 21.1.2 to 21.1.3 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.1.3/packages/elements) Updates `@angular/forms` from 21.1.2 to 21.1.3 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.1.3/packages/forms) Updates `@angular/platform-browser` from 21.1.2 to 21.1.3 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.1.3/packages/platform-browser) Updates `@angular/platform-browser-dynamic` from 21.1.2 to 21.1.3 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.1.3/packages/platform-browser-dynamic) Updates `@angular/router` from 21.1.2 to 21.1.3 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.1.3/packages/router) Updates `@angular-devkit/build-angular` from 21.1.2 to 21.1.3 - [Release notes](https://github.com/angular/angular-cli/releases) - [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular-cli/compare/v21.1.2...v21.1.3) Updates `@angular/language-service` from 21.1.2 to 21.1.3 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.1.3/packages/language-service) --- updated-dependencies: - dependency-name: "@angular/animations" dependency-version: 21.1.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/cdk" dependency-version: 21.1.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/cli" dependency-version: 21.1.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/common" dependency-version: 21.1.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/compiler" dependency-version: 21.1.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/compiler-cli" dependency-version: 21.1.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/core" dependency-version: 21.1.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/elements" dependency-version: 21.1.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/forms" dependency-version: 21.1.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/platform-browser" dependency-version: 21.1.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/platform-browser-dynamic" dependency-version: 21.1.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/router" dependency-version: 21.1.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular-devkit/build-angular" dependency-version: 21.1.3 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: angular - dependency-name: "@angular/language-service" dependency-version: 21.1.3 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: angular ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 768 ++++++++++++++++++------------------- frontend/package.json | 28 +- 2 files changed, 390 insertions(+), 406 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4de3f2bce46..fde984b292b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,18 +9,18 @@ "version": "0.1.0", "license": "GPLv3", "dependencies": { - "@angular/animations": "^21.1.2", - "@angular/cdk": "^21.1.2", - "@angular/cli": "^21.1.2", - "@angular/common": "^21.1.2", - "@angular/compiler": "^21.1.2", - "@angular/compiler-cli": "^21.1.2", - "@angular/core": "^21.1.2", - "@angular/elements": "^21.1.2", - "@angular/forms": "^21.1.2", - "@angular/platform-browser": "^21.1.2", - "@angular/platform-browser-dynamic": "^21.1.2", - "@angular/router": "^21.1.2", + "@angular/animations": "^21.1.3", + "@angular/cdk": "^21.1.3", + "@angular/cli": "^21.1.3", + "@angular/common": "^21.1.3", + "@angular/compiler": "^21.1.3", + "@angular/compiler-cli": "^21.1.3", + "@angular/core": "^21.1.3", + "@angular/elements": "^21.1.3", + "@angular/forms": "^21.1.3", + "@angular/platform-browser": "^21.1.3", + "@angular/platform-browser-dynamic": "^21.1.3", + "@angular/router": "^21.1.3", "@appsignal/javascript": "^1.6.1", "@appsignal/plugin-breadcrumbs-console": "^1.1.37", "@appsignal/plugin-breadcrumbs-network": "^1.1.24", @@ -128,13 +128,13 @@ }, "devDependencies": { "@angular-builders/custom-esbuild": "^21.0.3", - "@angular-devkit/build-angular": "^21.1.2", + "@angular-devkit/build-angular": "^21.1.3", "@angular-eslint/builder": "20.7.0", "@angular-eslint/eslint-plugin": "20.7.0", "@angular-eslint/eslint-plugin-template": "20.7.0", "@angular-eslint/schematics": "20.7.0", "@angular-eslint/template-parser": "20.7.0", - "@angular/language-service": "21.1.2", + "@angular/language-service": "21.1.3", "@eslint/js": "^9.39.2", "@html-eslint/eslint-plugin": "^0.54.2", "@html-eslint/parser": "^0.54.0", @@ -646,16 +646,16 @@ } }, "node_modules/@angular-devkit/build-angular": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-21.1.2.tgz", - "integrity": "sha512-i/FTbqVwj0Wk6B5RA2H9iVsDC/kIK/5koSEwkIQjXGZuDVFUoEuWiIR2PGGSSQ9u3DmkpVPZmKEXWRl+g7Qn5g==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-21.1.3.tgz", + "integrity": "sha512-02mA04tz9UshwPTv8lBkLcMPpMFh7YnAMXM6u0fL558rU7UrBxsm3XfMmDao3f+jT8umA1mDHBx9OW9LIF4Ewg==", "dev": true, "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2101.2", - "@angular-devkit/build-webpack": "0.2101.2", - "@angular-devkit/core": "21.1.2", - "@angular/build": "21.1.2", + "@angular-devkit/architect": "0.2101.3", + "@angular-devkit/build-webpack": "0.2101.3", + "@angular-devkit/core": "21.1.3", + "@angular/build": "21.1.3", "@babel/core": "7.28.5", "@babel/generator": "7.28.5", "@babel/helper-annotate-as-pure": "7.27.3", @@ -666,7 +666,7 @@ "@babel/preset-env": "7.28.5", "@babel/runtime": "7.28.4", "@discoveryjs/json-ext": "0.6.3", - "@ngtools/webpack": "21.1.2", + "@ngtools/webpack": "21.1.3", "ansi-colors": "4.1.3", "autoprefixer": "10.4.23", "babel-loader": "10.0.0", @@ -700,7 +700,7 @@ "tinyglobby": "0.2.15", "tree-kill": "1.2.2", "tslib": "2.8.1", - "webpack": "5.104.1", + "webpack": "5.105.0", "webpack-dev-middleware": "7.4.5", "webpack-dev-server": "5.2.2", "webpack-merge": "6.0.1", @@ -721,7 +721,7 @@ "@angular/platform-browser": "^21.0.0", "@angular/platform-server": "^21.0.0", "@angular/service-worker": "^21.0.0", - "@angular/ssr": "^21.1.2", + "@angular/ssr": "^21.1.3", "@web/test-runner": "^0.20.0", "browser-sync": "^3.0.2", "jest": "^30.2.0", @@ -778,12 +778,12 @@ } }, "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/architect": { - "version": "0.2101.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.2.tgz", - "integrity": "sha512-pV2onJgp16xO0vAqEfRWVynRPPLVHydYLANNa3UX3l5T39JcYdMIoOHSIIl8tWrxVeOwiWd1ajub0VsFTUok4Q==", + "version": "0.2101.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.3.tgz", + "integrity": "sha512-vKz8aPA62W+e9+pF6ct4CRDG/MjlIH7sWFGYkxPPRst2g46ZQsRkrzfMZAWv/wnt6OZ1OwyRuO3RW83EMhag8g==", "dev": true, "dependencies": { - "@angular-devkit/core": "21.1.2", + "@angular-devkit/core": "21.1.3", "rxjs": "7.8.2" }, "bin": { @@ -796,9 +796,9 @@ } }, "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.2.tgz", - "integrity": "sha512-0wl5nJlFWsbwfUB2CQeTSmnVQ8AtqqwM3bYPYtXSc+vA8+hzsOAjjDuRnBxZS9zTnqtXKXB1e7M3Iy7KUwh7LA==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.3.tgz", + "integrity": "sha512-huEXd1tWQHwwN+0VGRT+vSVplV0KNrGFUGJzkIW6iJE1SQElxn6etMai+pSd5DJcePkx6+SuscVsxbfwf70hnA==", "dev": true, "dependencies": { "ajv": "8.17.1", @@ -1044,12 +1044,12 @@ } }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.2101.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.2101.2.tgz", - "integrity": "sha512-/rC9rcrG+Tn8MZIEW9LTHmBuLiQdCHZyscgqgMXD049qgB858gS1Y/lP/tt0xrP3Yhan5XNcRYjcv6sYPtmPUw==", + "version": "0.2101.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.2101.3.tgz", + "integrity": "sha512-M2o79NbnrjKC78DBdPcJ/ZDSvTi1rpvWBhAa0TN/HZhW33xf9pkYCBOfHIowv+m/tPA1KqL7Ww3qNhRmzId6yg==", "dev": true, "dependencies": { - "@angular-devkit/architect": "0.2101.2", + "@angular-devkit/architect": "0.2101.3", "rxjs": "7.8.2" }, "engines": { @@ -1063,12 +1063,12 @@ } }, "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/architect": { - "version": "0.2101.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.2.tgz", - "integrity": "sha512-pV2onJgp16xO0vAqEfRWVynRPPLVHydYLANNa3UX3l5T39JcYdMIoOHSIIl8tWrxVeOwiWd1ajub0VsFTUok4Q==", + "version": "0.2101.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.3.tgz", + "integrity": "sha512-vKz8aPA62W+e9+pF6ct4CRDG/MjlIH7sWFGYkxPPRst2g46ZQsRkrzfMZAWv/wnt6OZ1OwyRuO3RW83EMhag8g==", "dev": true, "dependencies": { - "@angular-devkit/core": "21.1.2", + "@angular-devkit/core": "21.1.3", "rxjs": "7.8.2" }, "bin": { @@ -1081,9 +1081,9 @@ } }, "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/core": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.2.tgz", - "integrity": "sha512-0wl5nJlFWsbwfUB2CQeTSmnVQ8AtqqwM3bYPYtXSc+vA8+hzsOAjjDuRnBxZS9zTnqtXKXB1e7M3Iy7KUwh7LA==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.3.tgz", + "integrity": "sha512-huEXd1tWQHwwN+0VGRT+vSVplV0KNrGFUGJzkIW6iJE1SQElxn6etMai+pSd5DJcePkx6+SuscVsxbfwf70hnA==", "dev": true, "dependencies": { "ajv": "8.17.1", @@ -1371,9 +1371,9 @@ "license": "MIT" }, "node_modules/@angular/animations": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-21.1.2.tgz", - "integrity": "sha512-8lVSH3y/Pq22ND9ng80UQwQRiIPIE7oD3vuV98Wufld59+s5g4PdJNqPhEVD5dkYD0gYQcm3jTIXSeYuOfpsUg==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-21.1.3.tgz", + "integrity": "sha512-UADMncDd9lkmIT1NPVFcufyP5gJHMPzxNaQpojiGrxT1aT8Du30mao0KSrB4aTwcicv6/cdD5bZbIyg+FL6LkQ==", "dependencies": { "tslib": "^2.3.0" }, @@ -1381,17 +1381,17 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/core": "21.1.2" + "@angular/core": "21.1.3" } }, "node_modules/@angular/build": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-21.1.2.tgz", - "integrity": "sha512-5hl7OTZeQcdkr/3LXSijLuUCwlcqGyYJYOb8GbFqSifSR03JFI3tLQtyQ0LX2CXv3MOx1qFUQbYVfcjW5M36QQ==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-21.1.3.tgz", + "integrity": "sha512-RXVRuamfrSPwsFCLJgsO2ucp+dwWDbGbhXrQnMrGXm0qdgYpI8bAsBMd8wOeUA6vn4fRmjaRFQZbL/rcIVrkzw==", "dev": true, "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2101.2", + "@angular-devkit/architect": "0.2101.3", "@babel/core": "7.28.5", "@babel/helper-annotate-as-pure": "7.27.3", "@babel/helper-split-export-declaration": "7.24.7", @@ -1414,7 +1414,7 @@ "semver": "7.7.3", "source-map-support": "0.5.21", "tinyglobby": "0.2.15", - "undici": "7.18.2", + "undici": "7.20.0", "vite": "7.3.0", "watchpack": "2.5.0" }, @@ -1434,7 +1434,7 @@ "@angular/platform-browser": "^21.0.0", "@angular/platform-server": "^21.0.0", "@angular/service-worker": "^21.0.0", - "@angular/ssr": "^21.1.2", + "@angular/ssr": "^21.1.3", "karma": "^6.4.0", "less": "^4.2.0", "ng-packagr": "^21.0.0", @@ -1484,12 +1484,12 @@ } }, "node_modules/@angular/build/node_modules/@angular-devkit/architect": { - "version": "0.2101.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.2.tgz", - "integrity": "sha512-pV2onJgp16xO0vAqEfRWVynRPPLVHydYLANNa3UX3l5T39JcYdMIoOHSIIl8tWrxVeOwiWd1ajub0VsFTUok4Q==", + "version": "0.2101.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.3.tgz", + "integrity": "sha512-vKz8aPA62W+e9+pF6ct4CRDG/MjlIH7sWFGYkxPPRst2g46ZQsRkrzfMZAWv/wnt6OZ1OwyRuO3RW83EMhag8g==", "dev": true, "dependencies": { - "@angular-devkit/core": "21.1.2", + "@angular-devkit/core": "21.1.3", "rxjs": "7.8.2" }, "bin": { @@ -1502,9 +1502,9 @@ } }, "node_modules/@angular/build/node_modules/@angular-devkit/core": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.2.tgz", - "integrity": "sha512-0wl5nJlFWsbwfUB2CQeTSmnVQ8AtqqwM3bYPYtXSc+vA8+hzsOAjjDuRnBxZS9zTnqtXKXB1e7M3Iy7KUwh7LA==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.3.tgz", + "integrity": "sha512-huEXd1tWQHwwN+0VGRT+vSVplV0KNrGFUGJzkIW6iJE1SQElxn6etMai+pSd5DJcePkx6+SuscVsxbfwf70hnA==", "dev": true, "dependencies": { "ajv": "8.17.1", @@ -1585,9 +1585,9 @@ } }, "node_modules/@angular/cdk": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-21.1.2.tgz", - "integrity": "sha512-0q+PhBKmjKO0Yi353VCpMxT0g787cllLhdpyxh00i3twxNWvFkQZgy2Ih187ZXydvW+u9mFkK9+UGLzncQ0yng==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-21.1.3.tgz", + "integrity": "sha512-jMiEKCcZMIAnyx2jxrJHmw5c7JXAiN56ErZ4X+OuQ5yFvYRocRVEs25I0OMxntcXNdPTJQvpGwGlhWhS0yDorg==", "dependencies": { "parse5": "^8.0.0", "tslib": "^2.3.0" @@ -1624,17 +1624,17 @@ } }, "node_modules/@angular/cli": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-21.1.2.tgz", - "integrity": "sha512-AHjXCBl2PEilMJct6DX3ih5Fl5PiKpNDIj0ViTyVh1YcfpYjt6NzhVlV2o++8VNPNH/vMcmf2551LZIDProXXA==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-21.1.3.tgz", + "integrity": "sha512-UPtDcpKyrKZRPfym9gTovcibPzl2O/Woy7B8sm45sAnjDH+jDUCcCvuIak7GpH47shQkC2J4yvnHZbD4c6XxcQ==", "dependencies": { - "@angular-devkit/architect": "0.2101.2", - "@angular-devkit/core": "21.1.2", - "@angular-devkit/schematics": "21.1.2", + "@angular-devkit/architect": "0.2101.3", + "@angular-devkit/core": "21.1.3", + "@angular-devkit/schematics": "21.1.3", "@inquirer/prompts": "7.10.1", "@listr2/prompt-adapter-inquirer": "3.0.5", - "@modelcontextprotocol/sdk": "1.25.2", - "@schematics/angular": "21.1.2", + "@modelcontextprotocol/sdk": "1.26.0", + "@schematics/angular": "21.1.3", "@yarnpkg/lockfile": "1.1.0", "algoliasearch": "5.46.2", "ini": "6.0.0", @@ -1658,11 +1658,11 @@ } }, "node_modules/@angular/cli/node_modules/@angular-devkit/architect": { - "version": "0.2101.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.2.tgz", - "integrity": "sha512-pV2onJgp16xO0vAqEfRWVynRPPLVHydYLANNa3UX3l5T39JcYdMIoOHSIIl8tWrxVeOwiWd1ajub0VsFTUok4Q==", + "version": "0.2101.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.3.tgz", + "integrity": "sha512-vKz8aPA62W+e9+pF6ct4CRDG/MjlIH7sWFGYkxPPRst2g46ZQsRkrzfMZAWv/wnt6OZ1OwyRuO3RW83EMhag8g==", "dependencies": { - "@angular-devkit/core": "21.1.2", + "@angular-devkit/core": "21.1.3", "rxjs": "7.8.2" }, "bin": { @@ -1675,9 +1675,9 @@ } }, "node_modules/@angular/cli/node_modules/@angular-devkit/core": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.2.tgz", - "integrity": "sha512-0wl5nJlFWsbwfUB2CQeTSmnVQ8AtqqwM3bYPYtXSc+vA8+hzsOAjjDuRnBxZS9zTnqtXKXB1e7M3Iy7KUwh7LA==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.3.tgz", + "integrity": "sha512-huEXd1tWQHwwN+0VGRT+vSVplV0KNrGFUGJzkIW6iJE1SQElxn6etMai+pSd5DJcePkx6+SuscVsxbfwf70hnA==", "dependencies": { "ajv": "8.17.1", "ajv-formats": "3.0.1", @@ -1701,11 +1701,11 @@ } }, "node_modules/@angular/cli/node_modules/@angular-devkit/schematics": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.1.2.tgz", - "integrity": "sha512-PA3gkiFhHUuXd2XuP7yzKg/9N++bjw+uOl473KwIsMuZwMPhncKa4+mUYBaffDoPqaujZvjfo6mjtCBuiBv05w==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.1.3.tgz", + "integrity": "sha512-Ps7bRl5uOcM7WpNJHbSls/jz5/wAI0ldkTlKyiBFA7RtNeQIABAV+hvlw5DJuEb1Lo5hnK0hXj90AyZdOxzY+w==", "dependencies": { - "@angular-devkit/core": "21.1.2", + "@angular-devkit/core": "21.1.3", "jsonc-parser": "3.3.1", "magic-string": "0.30.21", "ora": "9.0.0", @@ -1946,9 +1946,9 @@ } }, "node_modules/@angular/common": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-21.1.2.tgz", - "integrity": "sha512-NK26OG1+/3EXLDWstSPmdGbkpt8bP9AsT9J7EBornMswUjmQDbjyb85N/esKjRjDMkw4p/aKpBo24eCV5uUmBA==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-21.1.3.tgz", + "integrity": "sha512-Wdbln/UqZM5oVnpfIydRdhhL8A9x3bKZ9Zy1/mM0q+qFSftPvmFZIXhEpFqbDwNYbGUhGzx7t8iULC4sVVp/zA==", "dependencies": { "tslib": "^2.3.0" }, @@ -1956,14 +1956,14 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/core": "21.1.2", + "@angular/core": "21.1.3", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/compiler": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-21.1.2.tgz", - "integrity": "sha512-5OFdZPNix7iK4HSdRxPgg74VvcmQZAMzv9ACYZ8iGfNxiJUjFSurfz0AtVEh0oE2oZDH1v48bHI1s+0ljCHZhA==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-21.1.3.tgz", + "integrity": "sha512-gDNLh7MEf7Qf88ktZzS4LJQXCA5U8aQTfK9ak+0mi2ruZ0x4XSjQCro4H6OPKrrbq94+6GcnlSX5+oVIajEY3w==", "dependencies": { "tslib": "^2.3.0" }, @@ -1972,9 +1972,9 @@ } }, "node_modules/@angular/compiler-cli": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-21.1.2.tgz", - "integrity": "sha512-h+sX7QvSz58KvmRwNMa33EZHti8Cnw1DL01kInJ/foDchC/O2VMOumeGHS+lAe48t2Nbhiq/obgf275TkDZYsA==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-21.1.3.tgz", + "integrity": "sha512-nKxoQ89W2B1WdonNQ9kgRnvLNS6DAxDrRHBslsKTlV+kbdv7h59M9PjT4ZZ2sp1M/M8LiofnUfa/s2jd/xYj5w==", "dependencies": { "@babel/core": "7.28.5", "@jridgewell/sourcemap-codec": "^1.4.14", @@ -1993,7 +1993,7 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/compiler": "21.1.2", + "@angular/compiler": "21.1.3", "typescript": ">=5.9 <6.0" }, "peerDependenciesMeta": { @@ -2150,9 +2150,9 @@ } }, "node_modules/@angular/core": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-21.1.2.tgz", - "integrity": "sha512-W2xxRb7noOD1DdMwKaZ3chFhii6nutaNIXt7dfWsMWoujg3Kqpdn1ukeyW5aHKQZvCJTIGr4f3whZ8Sj/17aCA==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-21.1.3.tgz", + "integrity": "sha512-TbhQxRC7Lb/3WBdm1n8KRsktmVEuGBBp0WRF5mq0Ze4s1YewIM6cULrSw9ACtcL5jdcq7c74ms+uKQsaP/gdcQ==", "dependencies": { "tslib": "^2.3.0" }, @@ -2160,7 +2160,7 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/compiler": "21.1.2", + "@angular/compiler": "21.1.3", "rxjs": "^6.5.3 || ^7.4.0", "zone.js": "~0.15.0 || ~0.16.0" }, @@ -2174,9 +2174,9 @@ } }, "node_modules/@angular/elements": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular/elements/-/elements-21.1.2.tgz", - "integrity": "sha512-x8RpuQHYVGKF5VuhRR/7ndeGS1vFt8r8PtkPaR1MobCxQkTr0MGfyXOB8wTrA/pvgXf2Yqv3apFyfNILnm9YrQ==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular/elements/-/elements-21.1.3.tgz", + "integrity": "sha512-nuXv4Nzmfl/m7d8shDCpSt7v1uKqWBj9QMNLpR8pzqa6I9cVyvT5fXVA0OF74b+3n8tzVActxcqtH+I8avt08A==", "dependencies": { "tslib": "^2.3.0" }, @@ -2184,14 +2184,14 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/core": "21.1.2", + "@angular/core": "21.1.3", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/forms": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-21.1.2.tgz", - "integrity": "sha512-dY56FuoBEvfLMtatKGg1vMFSwgySzWJm3URaBj3GpFTjhnuByHoxH4Lb5u50lrrVc9VQt/BZmq3mDZXjlx6Qgw==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-21.1.3.tgz", + "integrity": "sha512-YW/YdjM9suZUeJam9agHFXIEE3qQIhGYXMjnnX7xGjOe+CuR2R0qsWn1AR0yrKrNmFspb0lKgM7kTTJyzt8gZg==", "dependencies": { "@standard-schema/spec": "^1.0.0", "tslib": "^2.3.0" @@ -2200,25 +2200,25 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "21.1.2", - "@angular/core": "21.1.2", - "@angular/platform-browser": "21.1.2", + "@angular/common": "21.1.3", + "@angular/core": "21.1.3", + "@angular/platform-browser": "21.1.3", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/language-service": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-21.1.2.tgz", - "integrity": "sha512-/2VXz08k0BVQoYiDv/AyQgDY9AVzFuo29I/OAh28za58ReiXkT/WOWgP4el1rewX4uxWnM+BEpYxC3hcc+Ls0Q==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-21.1.3.tgz", + "integrity": "sha512-i7iMIMt2rbCDXRuVULbi0I5v4a7ldBgoGdPvHQ17poohTjU4NJ2Jm7p7mUYCGcDlYmWOvgxMGaoiqUs6S5lFPA==", "dev": true, "engines": { "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/@angular/platform-browser": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-21.1.2.tgz", - "integrity": "sha512-8vnCbQhxugQ3meGQ0YlSp0uNBYUjpFXYjFnGQ0Xq5jvzc9WX7KSix6+AydEjZtQfc1bWRetBTOlhQpqnwYp53g==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-21.1.3.tgz", + "integrity": "sha512-W+ZMXAioaP7CsACafBCHsIxiiKrRTPOlQ+hcC7XNBwy+bn5mjGONoCgLreQs76M8HNWLtr/OAUAr6h26OguOuA==", "dependencies": { "tslib": "^2.3.0" }, @@ -2226,9 +2226,9 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/animations": "21.1.2", - "@angular/common": "21.1.2", - "@angular/core": "21.1.2" + "@angular/animations": "21.1.3", + "@angular/common": "21.1.3", + "@angular/core": "21.1.3" }, "peerDependenciesMeta": { "@angular/animations": { @@ -2237,9 +2237,9 @@ } }, "node_modules/@angular/platform-browser-dynamic": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-21.1.2.tgz", - "integrity": "sha512-3+6Le0CuEpJFdJniD2ol6i9i7gmlJv+Qck5lxY+eHq2Ylj0VJ9sBIFaMBCmvdb6lz7QYnKoZr+Lhv1MX6hVXyg==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-21.1.3.tgz", + "integrity": "sha512-wWEjrNtJfxzZmbDWdJSyRau7NWpQ6IFM9QAyn7xH3cQDGCj+Gy9lTU5sUIYQc+7sx3nKWztolc7h/M5meYCTAg==", "dependencies": { "tslib": "^2.3.0" }, @@ -2247,16 +2247,16 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "21.1.2", - "@angular/compiler": "21.1.2", - "@angular/core": "21.1.2", - "@angular/platform-browser": "21.1.2" + "@angular/common": "21.1.3", + "@angular/compiler": "21.1.3", + "@angular/core": "21.1.3", + "@angular/platform-browser": "21.1.3" } }, "node_modules/@angular/router": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-21.1.2.tgz", - "integrity": "sha512-APl4tkTJIrpejlULLrGtIdLuJkNctPy0pnVijrJLR52nEV0xX165ulXk3XrL9QnMk0iy950aTYtoTal4aMw16Q==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-21.1.3.tgz", + "integrity": "sha512-uAw4LAMHXAPCe4SywhlUEWjMYVbbLHwTxLyduSp1b+9aVwep0juy5O/Xttlxd/oigVe0NMnOyJG9y1Br/ubnrg==", "dependencies": { "tslib": "^2.3.0" }, @@ -2264,9 +2264,9 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "21.1.2", - "@angular/core": "21.1.2", - "@angular/platform-browser": "21.1.2", + "@angular/common": "21.1.3", + "@angular/core": "21.1.3", + "@angular/platform-browser": "21.1.3", "rxjs": "^6.5.3 || ^7.4.0" } }, @@ -5154,10 +5154,9 @@ } }, "node_modules/@hono/node-server": { - "version": "1.19.8", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.8.tgz", - "integrity": "sha512-0/g2lIOPzX8f3vzW1ggQgvG5mjtFBDBHFAzI5SFAi2DzSqS9luJwqg9T6O/gKYLi+inS7eNxBeIFkkghIPvrMA==", - "license": "MIT", + "version": "1.19.9", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", + "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", "engines": { "node": ">=18.14.1" }, @@ -5751,7 +5750,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "dev": true, - "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -6253,12 +6251,11 @@ "integrity": "sha512-clYZdHcmRvMzVK5fjeDkQlHUzXQSNdZ7s4xOqC3nJPgz4C/TZkUecTo9YS4PruZqtDda/ag4erndP0MIn40dGA==" }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.25.2", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.25.2.tgz", - "integrity": "sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww==", - "license": "MIT", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz", + "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", "dependencies": { - "@hono/node-server": "^1.19.7", + "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", @@ -6266,14 +6263,15 @@ "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", - "express": "^5.0.1", - "express-rate-limit": "^7.5.0", - "jose": "^6.1.1", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", - "zod-to-json-schema": "^3.25.0" + "zod-to-json-schema": "^3.25.1" }, "engines": { "node": ">=18" @@ -6295,7 +6293,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" @@ -6308,7 +6305,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "license": "MIT", "dependencies": { "ajv": "^8.0.0" }, @@ -6325,7 +6321,6 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", - "license": "MIT", "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", @@ -6349,7 +6344,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", - "license": "MIT", "engines": { "node": ">=18" }, @@ -6362,7 +6356,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", "engines": { "node": ">=6.6.0" } @@ -6371,7 +6364,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -6388,7 +6380,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", "engines": { "node": ">= 0.8" } @@ -6397,7 +6388,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "license": "MIT", "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -6440,7 +6430,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", - "license": "MIT", "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", @@ -6461,7 +6450,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", "engines": { "node": ">= 0.8" } @@ -6470,7 +6458,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", @@ -6490,7 +6477,6 @@ "version": "0.7.2", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", - "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -6506,7 +6492,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "license": "MIT", "engines": { "node": ">= 0.8" } @@ -6515,7 +6500,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "license": "MIT", "engines": { "node": ">=18" }, @@ -6527,7 +6511,6 @@ "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -6536,7 +6519,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "license": "MIT", "dependencies": { "mime-db": "^1.54.0" }, @@ -6552,7 +6534,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -6561,7 +6542,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", - "license": "MIT", "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", @@ -6576,7 +6556,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", - "license": "MIT", "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", @@ -6602,7 +6581,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", - "license": "MIT", "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", @@ -6621,7 +6599,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", "engines": { "node": ">= 0.8" } @@ -6630,7 +6607,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "license": "MIT", "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", @@ -7087,9 +7063,9 @@ } }, "node_modules/@ngtools/webpack": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-21.1.2.tgz", - "integrity": "sha512-ZNMMD35urDKqYtx1drxPyGAvUPMOoiKjvrH8owpN+mzIO1nYpssCgmAseo1hePAduSvv8tAsY1NLtJfMSNzubw==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-21.1.3.tgz", + "integrity": "sha512-Un4dHHELxuFwlSfjsHlmw73col+t0NID2hhx1JPRmKXBXAd4nDRJKX2LPouQLL0FFF+gOtA4mxabf5NruDTQNg==", "dev": true, "engines": { "node": "^20.19.0 || ^22.12.0 || >=24.0.0", @@ -8386,12 +8362,12 @@ "dev": true }, "node_modules/@schematics/angular": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-21.1.2.tgz", - "integrity": "sha512-kxwxhCIUrj7DfzEtDSs/pi/w+aII/WQLpPfLgoQCWE8/95v60WnTfd1afmsXsFoxikKPxkwoPWtU2YbhSoX9MQ==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-21.1.3.tgz", + "integrity": "sha512-obJvWBhzRdsYL2msM4+8bQD21vFl3VxaVsuiq6iIfYsxhU5i2Iar2wM9NaRaIIqAYhZ8ehQQ/moB9BEbWvDCTw==", "dependencies": { - "@angular-devkit/core": "21.1.2", - "@angular-devkit/schematics": "21.1.2", + "@angular-devkit/core": "21.1.3", + "@angular-devkit/schematics": "21.1.3", "jsonc-parser": "3.3.1" }, "engines": { @@ -8401,9 +8377,9 @@ } }, "node_modules/@schematics/angular/node_modules/@angular-devkit/core": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.2.tgz", - "integrity": "sha512-0wl5nJlFWsbwfUB2CQeTSmnVQ8AtqqwM3bYPYtXSc+vA8+hzsOAjjDuRnBxZS9zTnqtXKXB1e7M3Iy7KUwh7LA==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.3.tgz", + "integrity": "sha512-huEXd1tWQHwwN+0VGRT+vSVplV0KNrGFUGJzkIW6iJE1SQElxn6etMai+pSd5DJcePkx6+SuscVsxbfwf70hnA==", "dependencies": { "ajv": "8.17.1", "ajv-formats": "3.0.1", @@ -8427,11 +8403,11 @@ } }, "node_modules/@schematics/angular/node_modules/@angular-devkit/schematics": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.1.2.tgz", - "integrity": "sha512-PA3gkiFhHUuXd2XuP7yzKg/9N++bjw+uOl473KwIsMuZwMPhncKa4+mUYBaffDoPqaujZvjfo6mjtCBuiBv05w==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.1.3.tgz", + "integrity": "sha512-Ps7bRl5uOcM7WpNJHbSls/jz5/wAI0ldkTlKyiBFA7RtNeQIABAV+hvlw5DJuEb1Lo5hnK0hXj90AyZdOxzY+w==", "dependencies": { - "@angular-devkit/core": "21.1.2", + "@angular-devkit/core": "21.1.3", "jsonc-parser": "3.3.1", "magic-string": "0.30.21", "ora": "9.0.0", @@ -9194,7 +9170,6 @@ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, - "license": "MIT", "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -9205,7 +9180,6 @@ "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "dev": true, - "license": "MIT", "dependencies": { "@types/eslint": "*", "@types/estree": "*" @@ -10481,7 +10455,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "dev": true, - "license": "MIT", "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" @@ -10491,29 +10464,25 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "dev": true, - "license": "MIT", "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", @@ -10524,15 +10493,13 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "dev": true, - "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -10545,7 +10512,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "dev": true, - "license": "MIT", "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -10555,7 +10521,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@xtuc/long": "4.2.2" } @@ -10564,15 +10529,13 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "dev": true, - "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -10589,7 +10552,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "dev": true, - "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", @@ -10603,7 +10565,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "dev": true, - "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -10616,7 +10577,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "dev": true, - "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-api-error": "1.13.2", @@ -10631,7 +10591,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "dev": true, - "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" @@ -10662,15 +10621,13 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true, - "license": "BSD-3-Clause" + "dev": true }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true, - "license": "Apache-2.0" + "dev": true }, "node_modules/@yarnpkg/lockfile": { "version": "1.1.0", @@ -10716,7 +10673,6 @@ "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=10.13.0" }, @@ -12196,7 +12152,6 @@ "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.0" } @@ -12403,8 +12358,7 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/compressible": { "version": "2.0.18", @@ -13488,14 +13442,13 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.18.4", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", - "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", + "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", "dev": true, - "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "tapable": "^2.3.0" }, "engines": { "node": ">=10.13.0" @@ -13692,8 +13645,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/es-object-atoms": { "version": "1.1.1", @@ -14470,7 +14422,6 @@ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.8.x" } @@ -14479,7 +14430,6 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", - "license": "MIT", "dependencies": { "eventsource-parser": "^3.0.1" }, @@ -14491,7 +14441,6 @@ "version": "3.0.6", "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", - "license": "MIT", "engines": { "node": ">=18.0.0" } @@ -14549,10 +14498,12 @@ } }, "node_modules/express-rate-limit": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", - "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", - "license": "MIT", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", + "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", + "dependencies": { + "ip-address": "10.0.1" + }, "engines": { "node": ">= 16" }, @@ -14563,6 +14514,14 @@ "express": ">= 4.11" } }, + "node_modules/express-rate-limit/node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "engines": { + "node": ">= 12" + } + }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -15642,6 +15601,14 @@ "hermes-estree": "0.25.1" } }, + "node_modules/hono": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.9.tgz", + "integrity": "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==", + "engines": { + "node": ">=16.9.0" + } + }, "node_modules/hosted-git-info": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", @@ -16521,8 +16488,7 @@ "node_modules/is-promise": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT" + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" }, "node_modules/is-regex": { "version": "1.2.1", @@ -16893,7 +16859,6 @@ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, - "license": "MIT", "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -16908,7 +16873,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, - "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -16933,7 +16897,6 @@ "version": "6.1.3", "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", - "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" } @@ -17011,8 +16974,7 @@ "node_modules/json-schema-typed": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", - "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", - "license": "BSD-2-Clause" + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -17776,7 +17738,6 @@ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.11.5" }, @@ -18519,8 +18480,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/methods": { "version": "1.1.2", @@ -20592,7 +20552,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", - "license": "MIT", "engines": { "node": ">=16.20.0" } @@ -22003,7 +21962,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "license": "MIT", "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", @@ -22019,7 +21977,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -22036,7 +21993,6 @@ "version": "8.3.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", - "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/express" @@ -23484,7 +23440,6 @@ "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", @@ -23503,7 +23458,6 @@ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", "dev": true, - "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", @@ -23538,7 +23492,6 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -23551,7 +23504,6 @@ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "dev": true, - "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -24356,9 +24308,9 @@ } }, "node_modules/undici": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.18.2.tgz", - "integrity": "sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw==", + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.20.0.tgz", + "integrity": "sha512-MJZrkjyd7DeC+uPZh+5/YaMDxFiiEEaDgbUSVMXayofAkDWF1088CDo+2RPg7B1BuS1qf1vgNE7xqwPxE0DuSQ==", "dev": true, "engines": { "node": ">=20.18.1" @@ -24963,11 +24915,10 @@ } }, "node_modules/webpack": { - "version": "5.104.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.104.1.tgz", - "integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==", + "version": "5.105.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz", + "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==", "dev": true, - "license": "MIT", "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -24979,7 +24930,7 @@ "acorn-import-phases": "^1.0.3", "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.4", + "enhanced-resolve": "^5.19.0", "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -24992,7 +24943,7 @@ "schema-utils": "^4.3.3", "tapable": "^2.3.0", "terser-webpack-plugin": "^5.3.16", - "watchpack": "^2.4.4", + "watchpack": "^2.5.1", "webpack-sources": "^3.3.3" }, "bin": { @@ -25302,7 +25253,6 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -25315,7 +25265,6 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -25329,7 +25278,6 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, - "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -25338,15 +25286,13 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/webpack/node_modules/schema-utils": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "dev": true, - "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -25361,6 +25307,19 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/webpack/node_modules/watchpack": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", @@ -25742,7 +25701,6 @@ "version": "3.25.1", "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", - "license": "ISC", "peerDependencies": { "zod": "^3.25 || ^4" } @@ -26062,16 +26020,16 @@ } }, "@angular-devkit/build-angular": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-21.1.2.tgz", - "integrity": "sha512-i/FTbqVwj0Wk6B5RA2H9iVsDC/kIK/5koSEwkIQjXGZuDVFUoEuWiIR2PGGSSQ9u3DmkpVPZmKEXWRl+g7Qn5g==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-21.1.3.tgz", + "integrity": "sha512-02mA04tz9UshwPTv8lBkLcMPpMFh7YnAMXM6u0fL558rU7UrBxsm3XfMmDao3f+jT8umA1mDHBx9OW9LIF4Ewg==", "dev": true, "requires": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2101.2", - "@angular-devkit/build-webpack": "0.2101.2", - "@angular-devkit/core": "21.1.2", - "@angular/build": "21.1.2", + "@angular-devkit/architect": "0.2101.3", + "@angular-devkit/build-webpack": "0.2101.3", + "@angular-devkit/core": "21.1.3", + "@angular/build": "21.1.3", "@babel/core": "7.28.5", "@babel/generator": "7.28.5", "@babel/helper-annotate-as-pure": "7.27.3", @@ -26082,7 +26040,7 @@ "@babel/preset-env": "7.28.5", "@babel/runtime": "7.28.4", "@discoveryjs/json-ext": "0.6.3", - "@ngtools/webpack": "21.1.2", + "@ngtools/webpack": "21.1.3", "ansi-colors": "4.1.3", "autoprefixer": "10.4.23", "babel-loader": "10.0.0", @@ -26117,7 +26075,7 @@ "tinyglobby": "0.2.15", "tree-kill": "1.2.2", "tslib": "2.8.1", - "webpack": "5.104.1", + "webpack": "5.105.0", "webpack-dev-middleware": "7.4.5", "webpack-dev-server": "5.2.2", "webpack-merge": "6.0.1", @@ -26125,19 +26083,19 @@ }, "dependencies": { "@angular-devkit/architect": { - "version": "0.2101.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.2.tgz", - "integrity": "sha512-pV2onJgp16xO0vAqEfRWVynRPPLVHydYLANNa3UX3l5T39JcYdMIoOHSIIl8tWrxVeOwiWd1ajub0VsFTUok4Q==", + "version": "0.2101.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.3.tgz", + "integrity": "sha512-vKz8aPA62W+e9+pF6ct4CRDG/MjlIH7sWFGYkxPPRst2g46ZQsRkrzfMZAWv/wnt6OZ1OwyRuO3RW83EMhag8g==", "dev": true, "requires": { - "@angular-devkit/core": "21.1.2", + "@angular-devkit/core": "21.1.3", "rxjs": "7.8.2" } }, "@angular-devkit/core": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.2.tgz", - "integrity": "sha512-0wl5nJlFWsbwfUB2CQeTSmnVQ8AtqqwM3bYPYtXSc+vA8+hzsOAjjDuRnBxZS9zTnqtXKXB1e7M3Iy7KUwh7LA==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.3.tgz", + "integrity": "sha512-huEXd1tWQHwwN+0VGRT+vSVplV0KNrGFUGJzkIW6iJE1SQElxn6etMai+pSd5DJcePkx6+SuscVsxbfwf70hnA==", "dev": true, "requires": { "ajv": "8.17.1", @@ -26282,29 +26240,29 @@ } }, "@angular-devkit/build-webpack": { - "version": "0.2101.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.2101.2.tgz", - "integrity": "sha512-/rC9rcrG+Tn8MZIEW9LTHmBuLiQdCHZyscgqgMXD049qgB858gS1Y/lP/tt0xrP3Yhan5XNcRYjcv6sYPtmPUw==", + "version": "0.2101.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.2101.3.tgz", + "integrity": "sha512-M2o79NbnrjKC78DBdPcJ/ZDSvTi1rpvWBhAa0TN/HZhW33xf9pkYCBOfHIowv+m/tPA1KqL7Ww3qNhRmzId6yg==", "dev": true, "requires": { - "@angular-devkit/architect": "0.2101.2", + "@angular-devkit/architect": "0.2101.3", "rxjs": "7.8.2" }, "dependencies": { "@angular-devkit/architect": { - "version": "0.2101.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.2.tgz", - "integrity": "sha512-pV2onJgp16xO0vAqEfRWVynRPPLVHydYLANNa3UX3l5T39JcYdMIoOHSIIl8tWrxVeOwiWd1ajub0VsFTUok4Q==", + "version": "0.2101.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.3.tgz", + "integrity": "sha512-vKz8aPA62W+e9+pF6ct4CRDG/MjlIH7sWFGYkxPPRst2g46ZQsRkrzfMZAWv/wnt6OZ1OwyRuO3RW83EMhag8g==", "dev": true, "requires": { - "@angular-devkit/core": "21.1.2", + "@angular-devkit/core": "21.1.3", "rxjs": "7.8.2" } }, "@angular-devkit/core": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.2.tgz", - "integrity": "sha512-0wl5nJlFWsbwfUB2CQeTSmnVQ8AtqqwM3bYPYtXSc+vA8+hzsOAjjDuRnBxZS9zTnqtXKXB1e7M3Iy7KUwh7LA==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.3.tgz", + "integrity": "sha512-huEXd1tWQHwwN+0VGRT+vSVplV0KNrGFUGJzkIW6iJE1SQElxn6etMai+pSd5DJcePkx6+SuscVsxbfwf70hnA==", "dev": true, "requires": { "ajv": "8.17.1", @@ -26500,21 +26458,21 @@ } }, "@angular/animations": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-21.1.2.tgz", - "integrity": "sha512-8lVSH3y/Pq22ND9ng80UQwQRiIPIE7oD3vuV98Wufld59+s5g4PdJNqPhEVD5dkYD0gYQcm3jTIXSeYuOfpsUg==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-21.1.3.tgz", + "integrity": "sha512-UADMncDd9lkmIT1NPVFcufyP5gJHMPzxNaQpojiGrxT1aT8Du30mao0KSrB4aTwcicv6/cdD5bZbIyg+FL6LkQ==", "requires": { "tslib": "^2.3.0" } }, "@angular/build": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-21.1.2.tgz", - "integrity": "sha512-5hl7OTZeQcdkr/3LXSijLuUCwlcqGyYJYOb8GbFqSifSR03JFI3tLQtyQ0LX2CXv3MOx1qFUQbYVfcjW5M36QQ==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-21.1.3.tgz", + "integrity": "sha512-RXVRuamfrSPwsFCLJgsO2ucp+dwWDbGbhXrQnMrGXm0qdgYpI8bAsBMd8wOeUA6vn4fRmjaRFQZbL/rcIVrkzw==", "dev": true, "requires": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2101.2", + "@angular-devkit/architect": "0.2101.3", "@babel/core": "7.28.5", "@babel/helper-annotate-as-pure": "7.27.3", "@babel/helper-split-export-declaration": "7.24.7", @@ -26538,25 +26496,25 @@ "semver": "7.7.3", "source-map-support": "0.5.21", "tinyglobby": "0.2.15", - "undici": "7.18.2", + "undici": "7.20.0", "vite": "7.3.0", "watchpack": "2.5.0" }, "dependencies": { "@angular-devkit/architect": { - "version": "0.2101.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.2.tgz", - "integrity": "sha512-pV2onJgp16xO0vAqEfRWVynRPPLVHydYLANNa3UX3l5T39JcYdMIoOHSIIl8tWrxVeOwiWd1ajub0VsFTUok4Q==", + "version": "0.2101.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.3.tgz", + "integrity": "sha512-vKz8aPA62W+e9+pF6ct4CRDG/MjlIH7sWFGYkxPPRst2g46ZQsRkrzfMZAWv/wnt6OZ1OwyRuO3RW83EMhag8g==", "dev": true, "requires": { - "@angular-devkit/core": "21.1.2", + "@angular-devkit/core": "21.1.3", "rxjs": "7.8.2" } }, "@angular-devkit/core": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.2.tgz", - "integrity": "sha512-0wl5nJlFWsbwfUB2CQeTSmnVQ8AtqqwM3bYPYtXSc+vA8+hzsOAjjDuRnBxZS9zTnqtXKXB1e7M3Iy7KUwh7LA==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.3.tgz", + "integrity": "sha512-huEXd1tWQHwwN+0VGRT+vSVplV0KNrGFUGJzkIW6iJE1SQElxn6etMai+pSd5DJcePkx6+SuscVsxbfwf70hnA==", "dev": true, "requires": { "ajv": "8.17.1", @@ -26607,9 +26565,9 @@ } }, "@angular/cdk": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-21.1.2.tgz", - "integrity": "sha512-0q+PhBKmjKO0Yi353VCpMxT0g787cllLhdpyxh00i3twxNWvFkQZgy2Ih187ZXydvW+u9mFkK9+UGLzncQ0yng==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-21.1.3.tgz", + "integrity": "sha512-jMiEKCcZMIAnyx2jxrJHmw5c7JXAiN56ErZ4X+OuQ5yFvYRocRVEs25I0OMxntcXNdPTJQvpGwGlhWhS0yDorg==", "requires": { "parse5": "^8.0.0", "tslib": "^2.3.0" @@ -26631,17 +26589,17 @@ } }, "@angular/cli": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-21.1.2.tgz", - "integrity": "sha512-AHjXCBl2PEilMJct6DX3ih5Fl5PiKpNDIj0ViTyVh1YcfpYjt6NzhVlV2o++8VNPNH/vMcmf2551LZIDProXXA==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-21.1.3.tgz", + "integrity": "sha512-UPtDcpKyrKZRPfym9gTovcibPzl2O/Woy7B8sm45sAnjDH+jDUCcCvuIak7GpH47shQkC2J4yvnHZbD4c6XxcQ==", "requires": { - "@angular-devkit/architect": "0.2101.2", - "@angular-devkit/core": "21.1.2", - "@angular-devkit/schematics": "21.1.2", + "@angular-devkit/architect": "0.2101.3", + "@angular-devkit/core": "21.1.3", + "@angular-devkit/schematics": "21.1.3", "@inquirer/prompts": "7.10.1", "@listr2/prompt-adapter-inquirer": "3.0.5", - "@modelcontextprotocol/sdk": "1.25.2", - "@schematics/angular": "21.1.2", + "@modelcontextprotocol/sdk": "1.26.0", + "@schematics/angular": "21.1.3", "@yarnpkg/lockfile": "1.1.0", "algoliasearch": "5.46.2", "ini": "6.0.0", @@ -26657,18 +26615,18 @@ }, "dependencies": { "@angular-devkit/architect": { - "version": "0.2101.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.2.tgz", - "integrity": "sha512-pV2onJgp16xO0vAqEfRWVynRPPLVHydYLANNa3UX3l5T39JcYdMIoOHSIIl8tWrxVeOwiWd1ajub0VsFTUok4Q==", + "version": "0.2101.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.3.tgz", + "integrity": "sha512-vKz8aPA62W+e9+pF6ct4CRDG/MjlIH7sWFGYkxPPRst2g46ZQsRkrzfMZAWv/wnt6OZ1OwyRuO3RW83EMhag8g==", "requires": { - "@angular-devkit/core": "21.1.2", + "@angular-devkit/core": "21.1.3", "rxjs": "7.8.2" } }, "@angular-devkit/core": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.2.tgz", - "integrity": "sha512-0wl5nJlFWsbwfUB2CQeTSmnVQ8AtqqwM3bYPYtXSc+vA8+hzsOAjjDuRnBxZS9zTnqtXKXB1e7M3Iy7KUwh7LA==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.3.tgz", + "integrity": "sha512-huEXd1tWQHwwN+0VGRT+vSVplV0KNrGFUGJzkIW6iJE1SQElxn6etMai+pSd5DJcePkx6+SuscVsxbfwf70hnA==", "requires": { "ajv": "8.17.1", "ajv-formats": "3.0.1", @@ -26679,11 +26637,11 @@ } }, "@angular-devkit/schematics": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.1.2.tgz", - "integrity": "sha512-PA3gkiFhHUuXd2XuP7yzKg/9N++bjw+uOl473KwIsMuZwMPhncKa4+mUYBaffDoPqaujZvjfo6mjtCBuiBv05w==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.1.3.tgz", + "integrity": "sha512-Ps7bRl5uOcM7WpNJHbSls/jz5/wAI0ldkTlKyiBFA7RtNeQIABAV+hvlw5DJuEb1Lo5hnK0hXj90AyZdOxzY+w==", "requires": { - "@angular-devkit/core": "21.1.2", + "@angular-devkit/core": "21.1.3", "jsonc-parser": "3.3.1", "magic-string": "0.30.21", "ora": "9.0.0", @@ -26831,25 +26789,25 @@ } }, "@angular/common": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-21.1.2.tgz", - "integrity": "sha512-NK26OG1+/3EXLDWstSPmdGbkpt8bP9AsT9J7EBornMswUjmQDbjyb85N/esKjRjDMkw4p/aKpBo24eCV5uUmBA==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-21.1.3.tgz", + "integrity": "sha512-Wdbln/UqZM5oVnpfIydRdhhL8A9x3bKZ9Zy1/mM0q+qFSftPvmFZIXhEpFqbDwNYbGUhGzx7t8iULC4sVVp/zA==", "requires": { "tslib": "^2.3.0" } }, "@angular/compiler": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-21.1.2.tgz", - "integrity": "sha512-5OFdZPNix7iK4HSdRxPgg74VvcmQZAMzv9ACYZ8iGfNxiJUjFSurfz0AtVEh0oE2oZDH1v48bHI1s+0ljCHZhA==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-21.1.3.tgz", + "integrity": "sha512-gDNLh7MEf7Qf88ktZzS4LJQXCA5U8aQTfK9ak+0mi2ruZ0x4XSjQCro4H6OPKrrbq94+6GcnlSX5+oVIajEY3w==", "requires": { "tslib": "^2.3.0" } }, "@angular/compiler-cli": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-21.1.2.tgz", - "integrity": "sha512-h+sX7QvSz58KvmRwNMa33EZHti8Cnw1DL01kInJ/foDchC/O2VMOumeGHS+lAe48t2Nbhiq/obgf275TkDZYsA==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-21.1.3.tgz", + "integrity": "sha512-nKxoQ89W2B1WdonNQ9kgRnvLNS6DAxDrRHBslsKTlV+kbdv7h59M9PjT4ZZ2sp1M/M8LiofnUfa/s2jd/xYj5w==", "requires": { "@babel/core": "7.28.5", "@jridgewell/sourcemap-codec": "^1.4.14", @@ -26948,56 +26906,56 @@ } }, "@angular/core": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-21.1.2.tgz", - "integrity": "sha512-W2xxRb7noOD1DdMwKaZ3chFhii6nutaNIXt7dfWsMWoujg3Kqpdn1ukeyW5aHKQZvCJTIGr4f3whZ8Sj/17aCA==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-21.1.3.tgz", + "integrity": "sha512-TbhQxRC7Lb/3WBdm1n8KRsktmVEuGBBp0WRF5mq0Ze4s1YewIM6cULrSw9ACtcL5jdcq7c74ms+uKQsaP/gdcQ==", "requires": { "tslib": "^2.3.0" } }, "@angular/elements": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular/elements/-/elements-21.1.2.tgz", - "integrity": "sha512-x8RpuQHYVGKF5VuhRR/7ndeGS1vFt8r8PtkPaR1MobCxQkTr0MGfyXOB8wTrA/pvgXf2Yqv3apFyfNILnm9YrQ==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular/elements/-/elements-21.1.3.tgz", + "integrity": "sha512-nuXv4Nzmfl/m7d8shDCpSt7v1uKqWBj9QMNLpR8pzqa6I9cVyvT5fXVA0OF74b+3n8tzVActxcqtH+I8avt08A==", "requires": { "tslib": "^2.3.0" } }, "@angular/forms": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-21.1.2.tgz", - "integrity": "sha512-dY56FuoBEvfLMtatKGg1vMFSwgySzWJm3URaBj3GpFTjhnuByHoxH4Lb5u50lrrVc9VQt/BZmq3mDZXjlx6Qgw==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-21.1.3.tgz", + "integrity": "sha512-YW/YdjM9suZUeJam9agHFXIEE3qQIhGYXMjnnX7xGjOe+CuR2R0qsWn1AR0yrKrNmFspb0lKgM7kTTJyzt8gZg==", "requires": { "@standard-schema/spec": "^1.0.0", "tslib": "^2.3.0" } }, "@angular/language-service": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-21.1.2.tgz", - "integrity": "sha512-/2VXz08k0BVQoYiDv/AyQgDY9AVzFuo29I/OAh28za58ReiXkT/WOWgP4el1rewX4uxWnM+BEpYxC3hcc+Ls0Q==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-21.1.3.tgz", + "integrity": "sha512-i7iMIMt2rbCDXRuVULbi0I5v4a7ldBgoGdPvHQ17poohTjU4NJ2Jm7p7mUYCGcDlYmWOvgxMGaoiqUs6S5lFPA==", "dev": true }, "@angular/platform-browser": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-21.1.2.tgz", - "integrity": "sha512-8vnCbQhxugQ3meGQ0YlSp0uNBYUjpFXYjFnGQ0Xq5jvzc9WX7KSix6+AydEjZtQfc1bWRetBTOlhQpqnwYp53g==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-21.1.3.tgz", + "integrity": "sha512-W+ZMXAioaP7CsACafBCHsIxiiKrRTPOlQ+hcC7XNBwy+bn5mjGONoCgLreQs76M8HNWLtr/OAUAr6h26OguOuA==", "requires": { "tslib": "^2.3.0" } }, "@angular/platform-browser-dynamic": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-21.1.2.tgz", - "integrity": "sha512-3+6Le0CuEpJFdJniD2ol6i9i7gmlJv+Qck5lxY+eHq2Ylj0VJ9sBIFaMBCmvdb6lz7QYnKoZr+Lhv1MX6hVXyg==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-21.1.3.tgz", + "integrity": "sha512-wWEjrNtJfxzZmbDWdJSyRau7NWpQ6IFM9QAyn7xH3cQDGCj+Gy9lTU5sUIYQc+7sx3nKWztolc7h/M5meYCTAg==", "requires": { "tslib": "^2.3.0" } }, "@angular/router": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-21.1.2.tgz", - "integrity": "sha512-APl4tkTJIrpejlULLrGtIdLuJkNctPy0pnVijrJLR52nEV0xX165ulXk3XrL9QnMk0iy950aTYtoTal4aMw16Q==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-21.1.3.tgz", + "integrity": "sha512-uAw4LAMHXAPCe4SywhlUEWjMYVbbLHwTxLyduSp1b+9aVwep0juy5O/Xttlxd/oigVe0NMnOyJG9y1Br/ubnrg==", "requires": { "tslib": "^2.3.0" } @@ -28842,9 +28800,9 @@ } }, "@hono/node-server": { - "version": "1.19.8", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.8.tgz", - "integrity": "sha512-0/g2lIOPzX8f3vzW1ggQgvG5mjtFBDBHFAzI5SFAi2DzSqS9luJwqg9T6O/gKYLi+inS7eNxBeIFkkghIPvrMA==" + "version": "1.19.9", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", + "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==" }, "@hotwired/stimulus": { "version": "3.2.2", @@ -29521,11 +29479,11 @@ "integrity": "sha512-clYZdHcmRvMzVK5fjeDkQlHUzXQSNdZ7s4xOqC3nJPgz4C/TZkUecTo9YS4PruZqtDda/ag4erndP0MIn40dGA==" }, "@modelcontextprotocol/sdk": { - "version": "1.25.2", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.25.2.tgz", - "integrity": "sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww==", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz", + "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", "requires": { - "@hono/node-server": "^1.19.7", + "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", @@ -29533,14 +29491,15 @@ "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", - "express": "^5.0.1", - "express-rate-limit": "^7.5.0", - "jose": "^6.1.1", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", - "zod-to-json-schema": "^3.25.0" + "zod-to-json-schema": "^3.25.1" }, "dependencies": { "accepts": { @@ -29981,9 +29940,9 @@ } }, "@ngtools/webpack": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-21.1.2.tgz", - "integrity": "sha512-ZNMMD35urDKqYtx1drxPyGAvUPMOoiKjvrH8owpN+mzIO1nYpssCgmAseo1hePAduSvv8tAsY1NLtJfMSNzubw==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-21.1.3.tgz", + "integrity": "sha512-Un4dHHELxuFwlSfjsHlmw73col+t0NID2hhx1JPRmKXBXAd4nDRJKX2LPouQLL0FFF+gOtA4mxabf5NruDTQNg==", "dev": true }, "@npmcli/agent": { @@ -30675,19 +30634,19 @@ "dev": true }, "@schematics/angular": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-21.1.2.tgz", - "integrity": "sha512-kxwxhCIUrj7DfzEtDSs/pi/w+aII/WQLpPfLgoQCWE8/95v60WnTfd1afmsXsFoxikKPxkwoPWtU2YbhSoX9MQ==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-21.1.3.tgz", + "integrity": "sha512-obJvWBhzRdsYL2msM4+8bQD21vFl3VxaVsuiq6iIfYsxhU5i2Iar2wM9NaRaIIqAYhZ8ehQQ/moB9BEbWvDCTw==", "requires": { - "@angular-devkit/core": "21.1.2", - "@angular-devkit/schematics": "21.1.2", + "@angular-devkit/core": "21.1.3", + "@angular-devkit/schematics": "21.1.3", "jsonc-parser": "3.3.1" }, "dependencies": { "@angular-devkit/core": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.2.tgz", - "integrity": "sha512-0wl5nJlFWsbwfUB2CQeTSmnVQ8AtqqwM3bYPYtXSc+vA8+hzsOAjjDuRnBxZS9zTnqtXKXB1e7M3Iy7KUwh7LA==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.3.tgz", + "integrity": "sha512-huEXd1tWQHwwN+0VGRT+vSVplV0KNrGFUGJzkIW6iJE1SQElxn6etMai+pSd5DJcePkx6+SuscVsxbfwf70hnA==", "requires": { "ajv": "8.17.1", "ajv-formats": "3.0.1", @@ -30698,11 +30657,11 @@ } }, "@angular-devkit/schematics": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.1.2.tgz", - "integrity": "sha512-PA3gkiFhHUuXd2XuP7yzKg/9N++bjw+uOl473KwIsMuZwMPhncKa4+mUYBaffDoPqaujZvjfo6mjtCBuiBv05w==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.1.3.tgz", + "integrity": "sha512-Ps7bRl5uOcM7WpNJHbSls/jz5/wAI0ldkTlKyiBFA7RtNeQIABAV+hvlw5DJuEb1Lo5hnK0hXj90AyZdOxzY+w==", "requires": { - "@angular-devkit/core": "21.1.2", + "@angular-devkit/core": "21.1.3", "jsonc-parser": "3.3.1", "magic-string": "0.30.21", "ora": "9.0.0", @@ -34147,13 +34106,13 @@ "dev": true }, "enhanced-resolve": { - "version": "5.18.4", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", - "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", + "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", "dev": true, "requires": { "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "tapable": "^2.3.0" } }, "ent": { @@ -35007,9 +34966,19 @@ } }, "express-rate-limit": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", - "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==" + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", + "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", + "requires": { + "ip-address": "10.0.1" + }, + "dependencies": { + "ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==" + } + } }, "ext": { "version": "1.7.0", @@ -35698,6 +35667,11 @@ "hermes-estree": "0.25.1" } }, + "hono": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.9.tgz", + "integrity": "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==" + }, "hosted-git-info": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", @@ -41603,9 +41577,9 @@ } }, "undici": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.18.2.tgz", - "integrity": "sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw==", + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.20.0.tgz", + "integrity": "sha512-MJZrkjyd7DeC+uPZh+5/YaMDxFiiEEaDgbUSVMXayofAkDWF1088CDo+2RPg7B1BuS1qf1vgNE7xqwPxE0DuSQ==", "dev": true }, "unicode-canonical-property-names-ecmascript": { @@ -41948,9 +41922,9 @@ "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==" }, "webpack": { - "version": "5.104.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.104.1.tgz", - "integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==", + "version": "5.105.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz", + "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==", "dev": true, "requires": { "@types/eslint-scope": "^3.7.7", @@ -41963,7 +41937,7 @@ "acorn-import-phases": "^1.0.3", "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.4", + "enhanced-resolve": "^5.19.0", "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -41976,7 +41950,7 @@ "schema-utils": "^4.3.3", "tapable": "^2.3.0", "terser-webpack-plugin": "^5.3.16", - "watchpack": "^2.4.4", + "watchpack": "^2.5.1", "webpack-sources": "^3.3.3" }, "dependencies": { @@ -42022,6 +41996,16 @@ "ajv-formats": "^2.1.1", "ajv-keywords": "^5.1.0" } + }, + "watchpack": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", + "dev": true, + "requires": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + } } } }, diff --git a/frontend/package.json b/frontend/package.json index d05d72662c6..016ac7c353c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,13 +6,13 @@ "private": true, "devDependencies": { "@angular-builders/custom-esbuild": "^21.0.3", - "@angular-devkit/build-angular": "^21.1.2", + "@angular-devkit/build-angular": "^21.1.3", "@angular-eslint/builder": "20.7.0", "@angular-eslint/eslint-plugin": "20.7.0", "@angular-eslint/eslint-plugin-template": "20.7.0", "@angular-eslint/schematics": "20.7.0", "@angular-eslint/template-parser": "20.7.0", - "@angular/language-service": "21.1.2", + "@angular/language-service": "21.1.3", "@eslint/js": "^9.39.2", "@html-eslint/eslint-plugin": "^0.54.2", "@html-eslint/parser": "^0.54.0", @@ -64,18 +64,18 @@ "wscat": "^6.1.0" }, "dependencies": { - "@angular/animations": "^21.1.2", - "@angular/cdk": "^21.1.2", - "@angular/cli": "^21.1.2", - "@angular/common": "^21.1.2", - "@angular/compiler": "^21.1.2", - "@angular/compiler-cli": "^21.1.2", - "@angular/core": "^21.1.2", - "@angular/elements": "^21.1.2", - "@angular/forms": "^21.1.2", - "@angular/platform-browser": "^21.1.2", - "@angular/platform-browser-dynamic": "^21.1.2", - "@angular/router": "^21.1.2", + "@angular/animations": "^21.1.3", + "@angular/cdk": "^21.1.3", + "@angular/cli": "^21.1.3", + "@angular/common": "^21.1.3", + "@angular/compiler": "^21.1.3", + "@angular/compiler-cli": "^21.1.3", + "@angular/core": "^21.1.3", + "@angular/elements": "^21.1.3", + "@angular/forms": "^21.1.3", + "@angular/platform-browser": "^21.1.3", + "@angular/platform-browser-dynamic": "^21.1.3", + "@angular/router": "^21.1.3", "@appsignal/javascript": "^1.6.1", "@appsignal/plugin-breadcrumbs-console": "^1.1.37", "@appsignal/plugin-breadcrumbs-network": "^1.1.24", From 6eecba5368d6e54340596143511125964f4d6714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Wed, 11 Feb 2026 06:50:37 +0100 Subject: [PATCH 245/293] Revert "Build hocuspocus as pre-step to docker build" --- .github/workflows/docker-release.yml | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index b73135fcd53..6d8be7b7167 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -16,7 +16,6 @@ jobs: outputs: tag: ${{ steps.compute.outputs.tag }} branch: ${{ steps.compute.outputs.branch }} - version: ${{ steps.compute.outputs.version }} steps: - name: Checkout uses: actions/checkout@v6 @@ -42,25 +41,11 @@ jobs: BRANCH_REF="dev" fi - # version strips the v prefix (v17.1.0 => 17.1.0) for hocuspocus docker tags - VERSION="${TAG_REF#v}" - echo "branch=$BRANCH_REF" | tee -a "$GITHUB_OUTPUT" echo "tag=$TAG_REF" | tee -a "$GITHUB_OUTPUT" - echo "version=$VERSION" | tee -a "$GITHUB_OUTPUT" - - build-hocuspocus: - needs: compute-inputs - uses: opf/op-blocknote-hocuspocus/.github/workflows/docker-build.yml@dev - with: - ref: refs/tags/${{ needs.compute-inputs.outputs.tag }} - tags: | - openproject/hocuspocus:latest - openproject/hocuspocus:${{ needs.compute-inputs.outputs.version }} - secrets: inherit build: - needs: [compute-inputs, build-hocuspocus] + needs: compute-inputs uses: ./.github/workflows/docker.yml with: branch: ${{ needs.compute-inputs.outputs.branch }} From 03541d1384f0a0f43cb51daf4f2e048b88f5e1ae Mon Sep 17 00:00:00 2001 From: OpenProject Actions CI Date: Wed, 11 Feb 2026 06:42:35 +0000 Subject: [PATCH 246/293] update locales from crowdin [ci skip] --- config/locales/crowdin/zh-CN.seeders.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/crowdin/zh-CN.seeders.yml b/config/locales/crowdin/zh-CN.seeders.yml index d6ad2b48763..336ac4fa27d 100644 --- a/config/locales/crowdin/zh-CN.seeders.yml +++ b/config/locales/crowdin/zh-CN.seeders.yml @@ -47,7 +47,7 @@ zh-CN: item_0: name: 项目查询查看器 item_1: - name: 项目查询查看器 + name: 项目查询编辑器 work_package_roles: item_0: name: 工作包编辑者 From 9f7334825de19f7376812623686f5f624fcb8d4b Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Wed, 11 Feb 2026 08:43:32 +0100 Subject: [PATCH 247/293] Change wrapper key generation --- .../open_project/common/inplace_edit_field_component.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/open_project/common/inplace_edit_field_component.rb b/app/components/open_project/common/inplace_edit_field_component.rb index 9e36edbd1b1..8eb1cb5dc3f 100644 --- a/app/components/open_project/common/inplace_edit_field_component.rb +++ b/app/components/open_project/common/inplace_edit_field_component.rb @@ -71,7 +71,7 @@ module OpenProject end def wrapper_key - "op-inplace-edit-field-component--#{@model.name.parameterize(separator: '_')}-#{model.id}--#{attribute.name}" + "op-inplace-edit-field-component--#{@model.class.name.parameterize(separator: '_')}-#{model.id}--#{attribute.name}" end def wrapper_test_selector From a81e87f40c99cab6a72b706856c8717d7bd7066e Mon Sep 17 00:00:00 2001 From: OpenProject Actions CI Date: Wed, 11 Feb 2026 08:08:37 +0000 Subject: [PATCH 248/293] update locales from crowdin [ci skip] --- config/locales/crowdin/de.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/locales/crowdin/de.yml b/config/locales/crowdin/de.yml index f52498b748e..220842a45be 100644 --- a/config/locales/crowdin/de.yml +++ b/config/locales/crowdin/de.yml @@ -621,8 +621,8 @@ de: new_label: "Neue Priorität" creation_wizard: errors: - no_work_package_type: "Failed to enable project initiation request because it requires at least one active work package type and this project has none. Please add at least one work package type to this project." - no_status_when_submitted: "Failed to enable project initiation request because work package type %{type} requires at least one status associated with it. Please enable at least one status workflow for this work package type." + no_work_package_type: "Die Anfrage zur Projektinitiierung konnte nicht aktiviert werden, da dafür mindestens ein aktiver Arbeitspaket-Typ erforderlich ist und dieses Projekt keinen hat. Bitte fügen Sie diesem Projekt mindestens einen Arbeitspaket-Typ hinzu." + no_status_when_submitted: "Der Antrag auf Projektinitiierung konnte nicht aktiviert werden, da für den Arbeitspaket-Typ %{type} mindestens ein Status erforderlich ist, der mit ihm verknüpft ist. Bitte aktivieren Sie mindestens einen Status-Workflow für diesen Arbeitspaket-Typ." export: description_attachment_export: "Das erzeugte Artefakt wird als PDF-Anhang zum erstellten Arbeitspaket gespeichert." description_file_link_export: "Das erstellte Arbeitspaket enthält einen Dateilink zu einer PDF-Datei, die in einem externen Dateispeicher gespeichert ist. Erfordert einen aktiven Dateispeicher mit automatisch verwalteten Projektordnern. Im Moment werden nur Nextcloud-Dateispeicher unterstützt." From 88bdd95201bbddc063260960d758cc59d3650471 Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Thu, 29 Jan 2026 15:11:03 +0100 Subject: [PATCH 249/293] Make project status BETA widget the default one --- .../grids/openproject-grids.module.ts | 4 -- .../project-status-beta.component.html | 13 ---- .../project-status-beta.component.ts | 41 ------------- .../project-status.component.html | 20 +------ .../project-status.component.ts | 60 ++----------------- .../grids/widgets/widgets.service.ts | 11 ---- modules/grids/config/locales/js-en.yml | 2 - .../in_project_base_registration.rb | 9 --- ...260129120330_remove_status_beta_widgets.rb | 48 +++++++++++++++ 9 files changed, 57 insertions(+), 151 deletions(-) delete mode 100644 frontend/src/app/shared/components/grids/widgets/project-status-beta/project-status-beta.component.html delete mode 100644 frontend/src/app/shared/components/grids/widgets/project-status-beta/project-status-beta.component.ts create mode 100644 modules/overviews/db/migrate/20260129120330_remove_status_beta_widgets.rb diff --git a/frontend/src/app/shared/components/grids/openproject-grids.module.ts b/frontend/src/app/shared/components/grids/openproject-grids.module.ts index 170920750df..70400628687 100644 --- a/frontend/src/app/shared/components/grids/openproject-grids.module.ts +++ b/frontend/src/app/shared/components/grids/openproject-grids.module.ts @@ -71,9 +71,6 @@ import { WidgetMembersComponent } from 'core-app/shared/components/grids/widgets import { WidgetProjectStatusComponent, } from 'core-app/shared/components/grids/widgets/project-status/project-status.component'; -import { - WidgetProjectStatusBetaComponent, -} from 'core-app/shared/components/grids/widgets/project-status-beta/project-status-beta.component'; import { OpenprojectTimeEntriesModule } from 'core-app/shared/components/time_entries/openproject-time-entries.module'; import { WidgetTimeEntriesCurrentUserMenuComponent, @@ -127,7 +124,6 @@ import { OpenprojectEnterpriseModule } from 'core-app/features/enterprise/openpr WidgetWpGraphComponent, WidgetProjectDescriptionComponent, WidgetProjectStatusComponent, - WidgetProjectStatusBetaComponent, WidgetSubprojectsComponent, WidgetProjectFavoritesComponent, WidgetTimeEntriesCurrentUserComponent, diff --git a/frontend/src/app/shared/components/grids/widgets/project-status-beta/project-status-beta.component.html b/frontend/src/app/shared/components/grids/widgets/project-status-beta/project-status-beta.component.html deleted file mode 100644 index 1d0e28d6a65..00000000000 --- a/frontend/src/app/shared/components/grids/widgets/project-status-beta/project-status-beta.component.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - diff --git a/frontend/src/app/shared/components/grids/widgets/project-status-beta/project-status-beta.component.ts b/frontend/src/app/shared/components/grids/widgets/project-status-beta/project-status-beta.component.ts deleted file mode 100644 index f5992b3195a..00000000000 --- a/frontend/src/app/shared/components/grids/widgets/project-status-beta/project-status-beta.component.ts +++ /dev/null @@ -1,41 +0,0 @@ -//-- copyright -// OpenProject is an open source project management software. -// Copyright (C) the OpenProject GmbH -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License version 3. -// -// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: -// Copyright (C) 2006-2013 Jean-Philippe Lang -// Copyright (C) 2010-2013 the ChiliProject Team -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -// -// See COPYRIGHT and LICENSE files for more details. -//++ - -import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { AbstractTurboWidgetComponent } from 'core-app/shared/components/grids/widgets/abstract-turbo-widget.component'; - -@Component({ - selector: 'op-project-status-beta-widget', - templateUrl: './project-status-beta.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - standalone: false, -}) -export class WidgetProjectStatusBetaComponent extends AbstractTurboWidgetComponent { - override frameId = 'grids-widgets-project-status'; - override name = 'project_status'; -} diff --git a/frontend/src/app/shared/components/grids/widgets/project-status/project-status.component.html b/frontend/src/app/shared/components/grids/widgets/project-status/project-status.component.html index ed6369a633b..1d0e28d6a65 100644 --- a/frontend/src/app/shared/components/grids/widgets/project-status/project-status.component.html +++ b/frontend/src/app/shared/components/grids/widgets/project-status/project-status.component.html @@ -3,25 +3,11 @@ [editable]="isEditable"> + attribute="status" + [attributeScope]="'Project'" /> -
    - @if ((project$ | async); as project) { - -
    - -
    -
    - -
    -
    - } -
    + diff --git a/frontend/src/app/shared/components/grids/widgets/project-status/project-status.component.ts b/frontend/src/app/shared/components/grids/widgets/project-status/project-status.component.ts index e70b532d8e9..43b15167193 100644 --- a/frontend/src/app/shared/components/grids/widgets/project-status/project-status.component.ts +++ b/frontend/src/app/shared/components/grids/widgets/project-status/project-status.component.ts @@ -26,64 +26,16 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - Injector, - OnInit, - ViewChild, -} from '@angular/core'; -import { AbstractWidgetComponent } from 'core-app/shared/components/grids/widgets/abstract-widget.component'; -import { I18nService } from 'core-app/core/i18n/i18n.service'; -import { CurrentProjectService } from 'core-app/core/current-project/current-project.service'; -import { ProjectResource } from 'core-app/features/hal/resources/project-resource'; -import { WorkPackageViewHighlightingService } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-highlighting.service'; -import { IsolatedQuerySpace } from 'core-app/features/work-packages/directives/query-space/isolated-query-space'; -import { Observable } from 'rxjs'; -import { HalResourceEditingService } from 'core-app/shared/components/fields/edit/services/hal-resource-editing.service'; -import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { AbstractTurboWidgetComponent } from 'core-app/shared/components/grids/widgets/abstract-turbo-widget.component'; @Component({ + selector: 'op-project-status-widget', templateUrl: './project-status.component.html', changeDetection: ChangeDetectionStrategy.OnPush, - providers: [ - WorkPackageViewHighlightingService, - IsolatedQuerySpace, - HalResourceEditingService, - ], standalone: false, }) -export class WidgetProjectStatusComponent extends AbstractWidgetComponent implements OnInit { - @ViewChild('contentContainer', { static: true }) readonly contentContainer:ElementRef; - - public currentStatusCode = 'not set'; - - public explanation = ''; - - public project$:Observable; - - constructor(protected readonly i18n:I18nService, - protected readonly injector:Injector, - protected readonly apiV3Service:ApiV3Service, - protected readonly currentProject:CurrentProjectService, - protected readonly cdRef:ChangeDetectorRef) { - super(i18n, injector); - } - - ngOnInit():void { - if (this.currentProject.id) { - this.project$ = this - .apiV3Service - .projects - .id(this.currentProject.id) - .get(); - this.cdRef.detectChanges(); - } - } - - public get isEditable():boolean { - return false; - } +export class WidgetProjectStatusComponent extends AbstractTurboWidgetComponent { + override frameId = 'grids-widgets-project-status'; + override name = 'project_status'; } diff --git a/frontend/src/app/shared/components/grids/widgets/widgets.service.ts b/frontend/src/app/shared/components/grids/widgets/widgets.service.ts index ab2b465f402..22092b1fe25 100644 --- a/frontend/src/app/shared/components/grids/widgets/widgets.service.ts +++ b/frontend/src/app/shared/components/grids/widgets/widgets.service.ts @@ -24,9 +24,6 @@ import { WidgetCustomTextComponent } from 'core-app/shared/components/grids/widg import { WidgetProjectStatusComponent, } from 'core-app/shared/components/grids/widgets/project-status/project-status.component'; -import { - WidgetProjectStatusBetaComponent, -} from 'core-app/shared/components/grids/widgets/project-status-beta/project-status-beta.component'; import { WidgetSubprojectsComponent } from 'core-app/shared/components/grids/widgets/subprojects/subprojects.component'; import { WidgetProjectFavoritesComponent, @@ -230,14 +227,6 @@ export class GridWidgetsService { name: this.I18n.t('js.grid.widgets.project_status.title'), }, }, - { - identifier: 'project_status_beta', - component: WidgetProjectStatusBetaComponent, - title: this.I18n.t('js.grid.widgets.project_status_beta.title'), - properties: { - name: this.I18n.t('js.grid.widgets.project_status_beta.title'), - }, - }, { identifier: 'subprojects', component: WidgetSubprojectsComponent, diff --git a/modules/grids/config/locales/js-en.yml b/modules/grids/config/locales/js-en.yml index 687b4ded2fc..0086ad71a69 100644 --- a/modules/grids/config/locales/js-en.yml +++ b/modules/grids/config/locales/js-en.yml @@ -27,8 +27,6 @@ en: not_set: 'Not set' finished: 'Finished' discontinued: 'Discontinued' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/lib/grids/configuration/in_project_base_registration.rb b/modules/grids/lib/grids/configuration/in_project_base_registration.rb index 675d3abdcdb..d57d685d218 100644 --- a/modules/grids/lib/grids/configuration/in_project_base_registration.rb +++ b/modules/grids/lib/grids/configuration/in_project_base_registration.rb @@ -4,7 +4,6 @@ module Grids::Configuration "work_packages_graph", "project_description", "project_status", - "project_status_beta", "subprojects", "work_packages_calendar", "work_packages_overview", @@ -27,10 +26,6 @@ module Grids::Configuration user.allowed_in_any_work_package?(:view_work_packages, in_project: project) } - view_beta_widgets = ->(_user, _project) { - OpenProject::FeatureDecisions.beta_widgets_active? - } - widget_strategy "work_packages_table" do after_destroy remove_query_lambda @@ -47,10 +42,6 @@ module Grids::Configuration options_representer "::API::V3::Grids::Widgets::ChartOptionsRepresenter" end - widget_strategy "project_status_beta" do - allowed view_beta_widgets - end - widget_strategy "custom_text" do options_representer "::API::V3::Grids::Widgets::CustomTextOptionsRepresenter" end diff --git a/modules/overviews/db/migrate/20260129120330_remove_status_beta_widgets.rb b/modules/overviews/db/migrate/20260129120330_remove_status_beta_widgets.rb new file mode 100644 index 00000000000..9eebb2bcfce --- /dev/null +++ b/modules/overviews/db/migrate/20260129120330_remove_status_beta_widgets.rb @@ -0,0 +1,48 @@ +# 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 RemoveStatusBetaWidgets < ActiveRecord::Migration[8.0] + def up + remove_status_beta_widgets + end + + def down + raise ActiveRecord::IrreversibleMigration + end + + private + + def remove_status_beta_widgets + execute <<-SQL.squish + DELETE FROM grid_widgets + WHERE identifier = 'project_status_beta' + SQL + end +end From 8916ed461cedcd6445f643903e852dda19d12342 Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Thu, 29 Jan 2026 15:12:42 +0100 Subject: [PATCH 250/293] Remove BETA widget feature flag --- config/initializers/feature_decisions.rb | 3 --- .../configuration/environment/README.md | 1 - 2 files changed, 4 deletions(-) diff --git a/config/initializers/feature_decisions.rb b/config/initializers/feature_decisions.rb index 51d3c11c9eb..e2519b3a26f 100644 --- a/config/initializers/feature_decisions.rb +++ b/config/initializers/feature_decisions.rb @@ -49,9 +49,6 @@ OpenProject::FeatureDecisions.add :calculated_value_project_attribute, description: "Allows the use of calculated values as a project attribute.", force_active: true -OpenProject::FeatureDecisions.add :beta_widgets, - description: "Enables BETA versions of widgets." - OpenProject::FeatureDecisions.add :mcp_server, description: "Enables the experimental MCP API." diff --git a/docs/installation-and-operations/configuration/environment/README.md b/docs/installation-and-operations/configuration/environment/README.md index 048fd4ce696..7151c50e829 100644 --- a/docs/installation-and-operations/configuration/environment/README.md +++ b/docs/installation-and-operations/configuration/environment/README.md @@ -220,7 +220,6 @@ OPENPROJECT_ENFORCE__TRACKING__START__AND__END__TIMES (default=false) Require st OPENPROJECT_ENTERPRISE__CHARGEBEE__SITE (default="openproject-enterprise") Site name for EE trial service OPENPROJECT_ENTERPRISE__PLAN (default="enterprise-on-premises---basic---euro---1-year") Default EE selected plan OPENPROJECT_ENTERPRISE__TRIAL__CREATION__HOST (default="https://start.openproject.com") Host for EE trial service -OPENPROJECT_FEATURE__BETA__WIDGETS__ACTIVE (default=false) Enables BETA versions of widgets. OPENPROJECT_FEATURE__BLOCK__NOTE__EDITOR__ACTIVE (default=false) Enables the block note editor for rich text fields where available. OPENPROJECT_FEATURE__BUILT__IN__OAUTH__APPLICATIONS__ACTIVE (default=false) Allows the display and use of built-in OAuth applications. OPENPROJECT_FEATURE__CALCULATED__VALUE__PROJECT__ATTRIBUTE__ACTIVE (default=true) Allows the use of calculated values as a project attribute. From fe7d4f950da12bbb304f8775c7fd9824abf592a9 Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Thu, 29 Jan 2026 15:28:58 +0100 Subject: [PATCH 251/293] Migrate description widget from Angular to Rails --- .../project-description.component.html | 10 +--- .../project-description.component.ts | 43 +++----------- .../grids/widgets/descriptions_controller.rb | 35 ++++++++++++ modules/grids/config/routes.rb | 1 + modules/grids/lib/grids/engine.rb | 1 + .../widgets/descriptions_controller_spec.rb | 56 +++++++++++++++++++ .../grids/spec/routing/widget_routing_spec.rb | 18 ++++++ .../project_description_widget_spec.rb | 8 +-- 8 files changed, 123 insertions(+), 49 deletions(-) create mode 100644 modules/grids/app/controllers/grids/widgets/descriptions_controller.rb create mode 100644 modules/grids/spec/controllers/grids/widgets/descriptions_controller_spec.rb diff --git a/frontend/src/app/shared/components/grids/widgets/project-description/project-description.component.html b/frontend/src/app/shared/components/grids/widgets/project-description/project-description.component.html index 92e974a9461..2e49d28a6e2 100644 --- a/frontend/src/app/shared/components/grids/widgets/project-description/project-description.component.html +++ b/frontend/src/app/shared/components/grids/widgets/project-description/project-description.component.html @@ -10,12 +10,4 @@ [resource]="resource" /> -
    - @if ((project$ | async); as project) { - - - - } -
    + diff --git a/frontend/src/app/shared/components/grids/widgets/project-description/project-description.component.ts b/frontend/src/app/shared/components/grids/widgets/project-description/project-description.component.ts index 6651b8e7773..e37a7b12174 100644 --- a/frontend/src/app/shared/components/grids/widgets/project-description/project-description.component.ts +++ b/frontend/src/app/shared/components/grids/widgets/project-description/project-description.component.ts @@ -27,47 +27,18 @@ //++ import { - ChangeDetectionStrategy, ChangeDetectorRef, Component, Injector, OnInit, + ChangeDetectionStrategy, + Component, } from '@angular/core'; -import { AbstractWidgetComponent } from 'core-app/shared/components/grids/widgets/abstract-widget.component'; -import { I18nService } from 'core-app/core/i18n/i18n.service'; -import { CurrentProjectService } from 'core-app/core/current-project/current-project.service'; -import { Observable } from 'rxjs'; -import { ProjectResource } from 'core-app/features/hal/resources/project-resource'; -import { HalResourceEditingService } from 'core-app/shared/components/fields/edit/services/hal-resource-editing.service'; -import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service'; +import { AbstractTurboWidgetComponent } from 'core-app/shared/components/grids/widgets/abstract-turbo-widget.component'; @Component({ + selector: 'op-project-description-widget', templateUrl: './project-description.component.html', changeDetection: ChangeDetectionStrategy.OnPush, - providers: [ - HalResourceEditingService, - ], standalone: false, }) -export class WidgetProjectDescriptionComponent extends AbstractWidgetComponent implements OnInit { - public project$:Observable; - - constructor(protected readonly i18n:I18nService, - protected readonly injector:Injector, - protected readonly apiV3Service:ApiV3Service, - protected readonly currentProject:CurrentProjectService, - protected readonly cdRef:ChangeDetectorRef) { - super(i18n, injector); - } - - ngOnInit():void { - if (this.currentProject.id) { - this.project$ = this - .apiV3Service - .projects - .id(this.currentProject.id) - .get(); - this.cdRef.detectChanges(); - } - } - - public get isEditable():boolean { - return false; - } +export class WidgetProjectDescriptionComponent extends AbstractTurboWidgetComponent { + override frameId = 'grids-widgets-description'; + override name = 'description'; } diff --git a/modules/grids/app/controllers/grids/widgets/descriptions_controller.rb b/modules/grids/app/controllers/grids/widgets/descriptions_controller.rb new file mode 100644 index 00000000000..03f6a511b8f --- /dev/null +++ b/modules/grids/app/controllers/grids/widgets/descriptions_controller.rb @@ -0,0 +1,35 @@ +# 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 Grids::Widgets::DescriptionsController < Grids::WidgetController + def show + render_widget Grids::Widgets::Description.new(@project, current_user:) + end +end diff --git a/modules/grids/config/routes.rb b/modules/grids/config/routes.rb index 205e12f9af5..a0d199211b8 100644 --- a/modules/grids/config/routes.rb +++ b/modules/grids/config/routes.rb @@ -37,6 +37,7 @@ Rails.application.routes.draw do resource :news, only: %i[show] resource :project_status, only: %i[show update] resource :subitems, only: %i[show] + resource :description, only: %i[show] end end diff --git a/modules/grids/lib/grids/engine.rb b/modules/grids/lib/grids/engine.rb index c641f173515..b60e7f4ac1f 100644 --- a/modules/grids/lib/grids/engine.rb +++ b/modules/grids/lib/grids/engine.rb @@ -16,6 +16,7 @@ module Grids .controller_actions .push( "grids/widgets/project_statuses/show", + "grids/widgets/descriptions/show", "grids/widgets/subitems/show" ) diff --git a/modules/grids/spec/controllers/grids/widgets/descriptions_controller_spec.rb b/modules/grids/spec/controllers/grids/widgets/descriptions_controller_spec.rb new file mode 100644 index 00000000000..de267b830b2 --- /dev/null +++ b/modules/grids/spec/controllers/grids/widgets/descriptions_controller_spec.rb @@ -0,0 +1,56 @@ +# 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 Grids::Widgets::DescriptionsController do + shared_let(:project) { create(:project) } + shared_let(:user) { create(:user, member_with_permissions: { project => %i[view_project] }) } + current_user { user } + + describe "GET #show" do + context "with project" do + let(:widget_instance) { instance_double(Grids::Widgets::Description, render_in: "content") } + + before do + allow(Grids::Widgets::Description) + .to receive(:new) + .and_return(widget_instance) + + get :show, params: { project_id: project } + end + + it "renders widget", :aggregate_failures do + expect(response).to be_successful + expect(response.body).to eq "content" + end + end + end +end diff --git a/modules/grids/spec/routing/widget_routing_spec.rb b/modules/grids/spec/routing/widget_routing_spec.rb index 2c22611d090..2e2ea4b15f6 100644 --- a/modules/grids/spec/routing/widget_routing_spec.rb +++ b/modules/grids/spec/routing/widget_routing_spec.rb @@ -85,6 +85,24 @@ RSpec.describe Grids::WidgetController do end end + describe "description routing" do + describe "GET #show" do + it do + expect(get("/projects/my-project/widgets/description")) + .to route_to(controller: "grids/widgets/descriptions", action: "show", project_id: "my-project") + end + end + end + + describe "description named routing" do + describe "GET #show" do + it do + expect(get(project_widgets_description_path("my-project"))) + .to route_to(controller: "grids/widgets/descriptions", action: "show", project_id: "my-project") + end + end + end + describe "news routing" do describe "GET #show" do context "for root" do diff --git a/modules/overviews/spec/features/project_description_widget_spec.rb b/modules/overviews/spec/features/project_description_widget_spec.rb index fa446185083..dce073b2335 100644 --- a/modules/overviews/spec/features/project_description_widget_spec.rb +++ b/modules/overviews/spec/features/project_description_widget_spec.rb @@ -80,9 +80,9 @@ RSpec.describe "Project description widget", :js, with_flag: { new_project_overv # Edit the project description within the widget within description_widget_area.area do # Find the editable description field - description_field = TextEditorField.new(page, "description", - selector: "op-editable-attribute-field[fieldname='description']") - + description_field = Turbo::TextEditorField.new(page, + "description", + selector: test_selector("op-overview-widget--project-description")) # Activate the field for editing description_field.activate! @@ -94,7 +94,7 @@ RSpec.describe "Project description widget", :js, with_flag: { new_project_overv description_field.save! end - dashboard_page.expect_and_dismiss_toaster message: I18n.t("js.notice_successful_update") + dashboard_page.expect_and_dismiss_flash message: I18n.t("js.notice_successful_update") dashboard_page.visit! expect(page).to have_content("This is a test project description with markdown formatting.") From 821b0f0ffeca8f909baff0b0ac6ae9547901c15f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Wed, 11 Feb 2026 08:47:28 +0100 Subject: [PATCH 252/293] Add better logging in model seeder --- app/seeders/basic_data/model_seeder.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/seeders/basic_data/model_seeder.rb b/app/seeders/basic_data/model_seeder.rb index e58c8288886..5aece770aee 100644 --- a/app/seeders/basic_data/model_seeder.rb +++ b/app/seeders/basic_data/model_seeder.rb @@ -52,6 +52,9 @@ module BasicData model_class .create!(model_attributes(model_data)) .tap { |model| seed_data.store_reference(model_data["reference"], model) } + rescue ActiveRecord::RecordInvalid => e + Rails.logger.error { "Failed to create #{model_class} seed_data: %e" } + raise e end def mapped_models_data From cd5d43b08f754eb38e423237305aa48c40f092ee Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Wed, 11 Feb 2026 09:25:36 +0100 Subject: [PATCH 253/293] Add id to wrapper key generation for uniqueness --- .../open_project/common/inplace_edit_field_component.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/components/open_project/common/inplace_edit_field_component.rb b/app/components/open_project/common/inplace_edit_field_component.rb index 8eb1cb5dc3f..64fe1e2e50f 100644 --- a/app/components/open_project/common/inplace_edit_field_component.rb +++ b/app/components/open_project/common/inplace_edit_field_component.rb @@ -41,6 +41,7 @@ module OpenProject @attribute = attribute @enforce_edit_mode = enforce_edit_mode @system_arguments = system_arguments + @system_arguments[:id] = system_arguments[:id] || SecureRandom.uuid end def field_class @@ -71,7 +72,8 @@ module OpenProject end def wrapper_key - "op-inplace-edit-field-component--#{@model.class.name.parameterize(separator: '_')}-#{model.id}--#{attribute.name}" + model_class = @model.class.name.parameterize(separator: "_") + "op-inplace-edit-field--#{model_class}-#{model.id}--#{attribute.name}--#{@system_arguments[:id]}" end def wrapper_test_selector From d482f1f7082c3240333d6b0032457cbd94f6f5ca Mon Sep 17 00:00:00 2001 From: Cyril Rohr Date: Wed, 11 Feb 2026 10:40:54 +0100 Subject: [PATCH 254/293] Fix docker bloat (#21948) * Refactor Docker build/runtime stages for slimmer images Split runtime and build dependencies into separate stages and build the app in a dedicated stage before runtime copy. Add a slim prune stage that removes non-runtime source trees, source maps, duplicate enterprise source videos, module test/doc folders, and extra vendored gem artifacts. This ensures bytes are removed before the final slim copy, so layer size actually decreases while keeping runtime behavior intact. * Add target-specific Docker image validation in CI Introduce script/ci/docker_validate_image.sh with validations for slim, slim-bim, and all-in-one images. Checks include runtime binary presence/absence, plugin asset/module integrity, slim pruning expectations, BIM tooling, and all-in-one API startup/embedded services. Update docker workflow to run the validator for every matrix target before push. * fix * Generate YAML-safe auto Hocuspocus secret All-in-one startup auto-generates OPENPROJECT_COLLABORATIVE__EDITING__HOCUSPOCUS__SECRET in the entrypoint. Environment overrides are parsed through YAML, so leading punctuation in the previous charset (e.g. %) could trigger Psych parsing errors and abort boot. Restrict generated secret characters to alphanumeric to keep parsing stable while preserving high entropy. * Fix all-in-one hocuspocus runtime and validation * Fix all-in-one memcached startup handover --- .dockerignore | 2 + .github/workflows/docker.yml | 21 +- docker/prod/Dockerfile | 48 +++- docker/prod/entrypoint.sh | 3 +- docker/prod/setup/bundle-install.sh | 5 + docker/prod/setup/preinstall-build.sh | 40 +++ docker/prod/setup/preinstall-common.sh | 126 ++++----- docker/prod/setup/prune-slim-runtime.sh | 38 +++ docker/prod/supervisord | 20 +- script/ci/docker_validate_image.sh | 323 ++++++++++++++++++++++++ 10 files changed, 540 insertions(+), 86 deletions(-) create mode 100755 docker/prod/setup/preinstall-build.sh create mode 100755 docker/prod/setup/prune-slim-runtime.sh create mode 100755 script/ci/docker_validate_image.sh diff --git a/.dockerignore b/.dockerignore index 6898dda39ab..5008cca0314 100644 --- a/.dockerignore +++ b/.dockerignore @@ -41,5 +41,7 @@ frontend/node_modules node_modules # travis vendor/bundle +# Local checkout; all-in-one copies hocuspocus from its dedicated image. +vendor/hocuspocus /public/assets /config/frontend_assets.manifest.json diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index f7f301dd62b..7c29ea86e23 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -255,23 +255,12 @@ jobs: if [ -d vendor/bundle.bak ]; then mv vendor/bundle.bak vendor/bundle fi - - name: Test - # We only test the native container. If that fails the builds for the others - # will be cancelled as well. - if: matrix.platform == 'linux/amd64' && matrix.target == 'all-in-one' + - name: Validate image run: | - docker run \ - --name openproject \ - -d -p 8080:80 --platform ${{ matrix.platform }} \ - -e SUPERVISORD_LOG_LEVEL=debug \ - -e OPENPROJECT_LOGIN__REQUIRED=false \ - -e OPENPROJECT_HTTPS=false \ - ${{ steps.build.outputs.imageid }} - - sleep 60 - - docker logs openproject --tail 300 - wget -O- --retry-on-http-error=503,502 --retry-connrefused http://localhost:8080/api/v3 + ./script/ci/docker_validate_image.sh \ + --image "${{ steps.build.outputs.imageid }}" \ + --target "${{ matrix.target }}" \ + --platform "${{ matrix.platform }}" - name: Push image id: push uses: docker/build-push-action@v6 diff --git a/docker/prod/Dockerfile b/docker/prod/Dockerfile index 082b9e0f90f..5b5c79b7e01 100755 --- a/docker/prod/Dockerfile +++ b/docker/prod/Dockerfile @@ -3,7 +3,7 @@ ARG DEBIAN_BASE="trixie" # Add SBOM scan context for intermediate steps ARG BUILDKIT_SBOM_SCAN_CONTEXT=true ARG BUILDKIT_SBOM_SCAN_STAGE=true -FROM ruby:${RUBY_VERSION}-slim-${DEBIAN_BASE} AS base +FROM ruby:${RUBY_VERSION}-slim-${DEBIAN_BASE} AS runtime-base LABEL maintainer="operations@openproject.com" ARG NODE_VERSION="22.21.0" @@ -19,6 +19,8 @@ ENV DOCKER=1 ENV APP_USER=app ENV APP_PATH=/app ENV APP_DATA_PATH=/var/openproject/assets +ENV BUNDLE_PATH="$APP_PATH/vendor/bundle" +ENV BUNDLE_APP_CONFIG="$APP_PATH/.bundle" ENV PGVERSION="17" ENV PGVERSION_CHOICES="13 15 17" ENV PGBIN="/usr/lib/postgresql/$PGVERSION/bin" @@ -54,10 +56,19 @@ WORKDIR $APP_PATH # upgrade bundler RUN gem install bundler --no-document -# system dependencies, nodejs +# runtime dependencies COPY ./docker/prod/setup/preinstall-common.sh ./docker/prod/setup/preinstall-common.sh RUN ./docker/prod/setup/preinstall-common.sh +FROM runtime-base AS build-base +ARG NODE_VERSION="22.21.0" + +# build-only dependencies +COPY ./docker/prod/setup/preinstall-build.sh ./docker/prod/setup/preinstall-build.sh +RUN ./docker/prod/setup/preinstall-build.sh + +FROM build-base AS app-build + # stuff required for gems COPY Gemfile Gemfile.* .ruby-version ./ COPY modules ./modules @@ -73,15 +84,32 @@ COPY . . # Copy lock file again as the updated version was overriden by COPY just now RUN cp Gemfile.lock.bak Gemfile.lock && rm Gemfile.lock.bak && \ - ./docker/prod/setup/precompile-assets.sh && \ - ./docker/prod/setup/postinstall-common.sh && \ + ./docker/prod/setup/precompile-assets.sh + +FROM app-build AS app-build-slim +COPY ./docker/prod/setup/prune-slim-runtime.sh ./docker/prod/setup/prune-slim-runtime.sh +RUN ./docker/prod/setup/prune-slim-runtime.sh + +FROM runtime-base AS app-runtime + +COPY --chown=$APP_USER:$APP_USER --from=app-build /app /app + +RUN ./docker/prod/setup/postinstall-common.sh && \ + cp ./config/database.production.yml config/database.yml && \ + ln -s $APP_PATH/docker/prod/setup/.irbrc /home/$APP_USER/ + +FROM runtime-base AS app-runtime-slim + +COPY --chown=$APP_USER:$APP_USER --from=app-build-slim /app /app + +RUN ./docker/prod/setup/postinstall-common.sh && \ cp ./config/database.production.yml config/database.yml && \ ln -s $APP_PATH/docker/prod/setup/.irbrc /home/$APP_USER/ # ------------------------------------- # slim (public) # ------------------------------------- -FROM base AS slim +FROM app-runtime-slim AS slim USER $APP_USER EXPOSE 8080 @@ -93,7 +121,7 @@ VOLUME ["$APP_DATA_PATH"] # slim-bim (public) # same as slim but with BIM support enabled # ------------------------------------- -FROM base AS slim-bim +FROM app-runtime-slim AS slim-bim USER $APP_USER EXPOSE 8080 @@ -104,7 +132,7 @@ ENV OPENPROJECT_EDITION=bim # ------------------------------------- # all-in-one (public) # ------------------------------------- -FROM base AS all-in-one +FROM app-runtime AS all-in-one ENV OPENPROJECT_RAILS__CACHE__STORE=memcache ENV DATABASE_URL=postgres://openproject:openproject@127.0.0.1/openproject @@ -114,8 +142,14 @@ COPY --from=openproject/gosu /go/bin/gosu /usr/local/bin/gosu RUN chmod +x /usr/local/bin/gosu && gosu nobody true COPY --from=openproject/hocuspocus:17.0.3 --chown=$APP_USER:$APP_USER /app /opt/hocuspocus +# Keep node/npm in all-in-one for bundled hocuspocus even when BIM support is disabled. +COPY --from=build-base /usr/local/bin/node /usr/local/bin/node +COPY --from=build-base /usr/local/lib/node_modules /usr/local/lib/node_modules RUN ./docker/prod/setup/postinstall-onprem.sh && \ + ln -sf ../lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm && \ + ln -sf ../lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx && \ + ln -sf ../lib/node_modules/corepack/dist/corepack.js /usr/local/bin/corepack && \ ln -s /app/docker/prod/setup/.irbrc /root/ # Expose ports for apache and postgres diff --git a/docker/prod/entrypoint.sh b/docker/prod/entrypoint.sh index d105d4494b7..0cc2e095162 100755 --- a/docker/prod/entrypoint.sh +++ b/docker/prod/entrypoint.sh @@ -97,7 +97,8 @@ if [ "$(id -u)" = '0' ]; then HP_HOST=${OPENPROJECT_HOST__NAME:="localhost"} export OPENPROJECT_COLLABORATIVE__EDITING__HOCUSPOCUS__URL="${HP_PROTOCOL}://${HP_HOST}/hocuspocus" - export OPENPROJECT_COLLABORATIVE__EDITING__HOCUSPOCUS__SECRET="$(tr -dc 'A-Za-z0-9!?%=' < /dev/urandom | head -c 32)" + # Use a YAML-safe secret charset because environment values are parsed via YAML. + export OPENPROJECT_COLLABORATIVE__EDITING__HOCUSPOCUS__SECRET="$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 32)" fi exec "$@" diff --git a/docker/prod/setup/bundle-install.sh b/docker/prod/setup/bundle-install.sh index 60140e09e77..f3b73b4d1af 100644 --- a/docker/prod/setup/bundle-install.sh +++ b/docker/prod/setup/bundle-install.sh @@ -10,3 +10,8 @@ cp Gemfile.lock Gemfile.lock.bak rm -rf vendor/bundle/ruby/*/cache rm -rf vendor/bundle/ruby/*/gems/*/spec rm -rf vendor/bundle/ruby/*/gems/*/test +rm -rf vendor/bundle/ruby/*/gems/*/tests +rm -rf vendor/bundle/ruby/*/gems/*/{doc,docs,example,examples,benchmark,benchmarks} +rm -rf vendor/bundle/ruby/*/bundler/gems/*/.git +rm -rf vendor/bundle/ruby/*/bundler/gems/*/{spec,test,tests,doc,docs,example,examples,benchmark,benchmarks} +find vendor/bundle -type f \( -name '*.a' -o -name '*.o' \) -delete diff --git a/docker/prod/setup/preinstall-build.sh b/docker/prod/setup/preinstall-build.sh new file mode 100755 index 00000000000..a20dd17793a --- /dev/null +++ b/docker/prod/setup/preinstall-build.sh @@ -0,0 +1,40 @@ +#!/bin/bash +set -euxo pipefail + +get_architecture() { + if command -v uname > /dev/null; then + ARCHITECTURE=$(uname -m) + case $ARCHITECTURE in + aarch64|arm64) + echo "arm64" + return 0 + ;; + ppc64le) + echo "ppc64le" + return 0 + ;; + esac + fi + + echo "x64" + return 0 +} + +ARCHITECTURE=$(get_architecture) + +apt-get update -qq +apt-get install -yq --no-install-recommends \ + ca-certificates \ + curl \ + git \ + build-essential \ + libyaml-dev \ + libpq-dev \ + libclang-dev + +if ! command -v node > /dev/null || ! command -v npm > /dev/null; then + curl -s https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-${ARCHITECTURE}.tar.gz | tar xzf - -C /usr/local --strip-components=1 +fi + +rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* +truncate -s 0 /var/log/*log diff --git a/docker/prod/setup/preinstall-common.sh b/docker/prod/setup/preinstall-common.sh index 77efda56922..61113401021 100755 --- a/docker/prod/setup/preinstall-common.sh +++ b/docker/prod/setup/preinstall-common.sh @@ -2,25 +2,24 @@ set -euxo pipefail get_architecture() { - if command -v uname > /dev/null; then - ARCHITECTURE=$(uname -m) - case $ARCHITECTURE in - aarch64|arm64) - echo "arm64" - return 0 - ;; - ppc64le) - echo "ppc64le" - return 0 - ;; - esac - fi + if command -v uname > /dev/null; then + ARCHITECTURE=$(uname -m) + case $ARCHITECTURE in + aarch64|arm64) + echo "arm64" + return 0 + ;; + ppc64le) + echo "ppc64le" + return 0 + ;; + esac + fi - echo "x64" - return 0 + echo "x64" + return 0 } -set -exo pipefail ARCHITECTURE=$(get_architecture) apt-get update -qq @@ -28,69 +27,76 @@ apt-get update -qq apt-get upgrade -y apt-get install -yq --no-install-recommends \ - curl \ - wget \ - file \ - gnupg2 \ - lsb-release + ca-certificates \ + curl \ + wget \ + file \ + gnupg2 \ + lsb-release -# install node + npm -curl -s https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-${ARCHITECTURE}.tar.gz | tar xzf - -C /usr/local --strip-components=1 - -wget --quiet -O- https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor -o /usr/share/keyrings/postgrsql.gpg - -echo "deb [signed-by=/usr/share/keyrings/postgrsql.gpg] http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list +wget --quiet -O- https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor -o /usr/share/keyrings/postgresql.gpg - +echo "deb [signed-by=/usr/share/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list apt-get update -qq apt-get install -yq --no-install-recommends \ - libpq-dev \ - libpq5 \ - libffi8 \ - unrtf \ - tesseract-ocr \ - poppler-utils \ - catdoc \ - imagemagick \ - libclang-dev \ - libjemalloc2 \ - git \ - build-essential \ - libyaml-dev \ + libpq5 \ + libffi8 \ + unrtf \ + tesseract-ocr \ + poppler-utils \ + catdoc \ + imagemagick \ + libjemalloc2 for version in $PGVERSION_CHOICES ; do - apt-get install -yq --no-install-recommends postgresql-client-$version + apt-get install -yq --no-install-recommends postgresql-client-$version done # Specifics for BIM edition if [ ! "$BIM_SUPPORT" = "false" ]; then - apt-get install -y wget unzip + apt-get install -yq --no-install-recommends unzip - tmpdir=$(mktemp -d) - cd $tmpdir + # Install node + npm for BIM runtime tools. + curl -s https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-${ARCHITECTURE}.tar.gz | tar xzf - -C /usr/local --strip-components=1 - # Install XKT converter - npm install -g @xeokit/xeokit-gltf-to-xkt@1.3.1 + tmpdir=$(mktemp -d) + cd $tmpdir - # Install COLLADA2GLTF - wget --no-verbose --tries 3 https://github.com/KhronosGroup/COLLADA2GLTF/releases/download/v2.1.5/COLLADA2GLTF-v2.1.5-linux.zip - unzip -q COLLADA2GLTF-v2.1.5-linux.zip - mv COLLADA2GLTF-bin "/usr/local/bin/COLLADA2GLTF" + # Install XKT converter + npm install -g @xeokit/xeokit-gltf-to-xkt@1.3.1 - # IFCconvert - wget --no-verbose --tries 3 https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.11-fea8e3a-linux64.zip - unzip -q IfcConvert-v0.7.11-fea8e3a-linux64.zip - mv IfcConvert "/usr/local/bin/IfcConvert" + # Install COLLADA2GLTF + wget --no-verbose --tries 3 https://github.com/KhronosGroup/COLLADA2GLTF/releases/download/v2.1.5/COLLADA2GLTF-v2.1.5-linux.zip + unzip -q COLLADA2GLTF-v2.1.5-linux.zip + mv COLLADA2GLTF-bin "/usr/local/bin/COLLADA2GLTF" - wget --no-verbose --tries 3 https://github.com/opf/xeokit-metadata/releases/download/v1.1.0/xeokit-metadata-linux-x64.tar.gz - tar -zxvf xeokit-metadata-linux-x64.tar.gz - chmod +x xeokit-metadata-linux-x64/xeokit-metadata - cp -r xeokit-metadata-linux-x64/ "/usr/lib/xeokit-metadata" - ln -s /usr/lib/xeokit-metadata/xeokit-metadata /usr/local/bin/xeokit-metadata + # IFCconvert + wget --no-verbose --tries 3 https://s3.amazonaws.com/ifcopenshell-builds/IfcConvert-v0.7.11-fea8e3a-linux64.zip + unzip -q IfcConvert-v0.7.11-fea8e3a-linux64.zip + mv IfcConvert "/usr/local/bin/IfcConvert" - cd / - rm -rf $tmpdir + wget --no-verbose --tries 3 https://github.com/opf/xeokit-metadata/releases/download/v1.1.0/xeokit-metadata-linux-x64.tar.gz + tar -zxvf xeokit-metadata-linux-x64.tar.gz + chmod +x xeokit-metadata-linux-x64/xeokit-metadata + cp -r xeokit-metadata-linux-x64/ "/usr/lib/xeokit-metadata" + ln -s /usr/lib/xeokit-metadata/xeokit-metadata /usr/local/bin/xeokit-metadata + + cd / + rm -rf $tmpdir fi id $APP_USER || useradd -d /home/$APP_USER -m $APP_USER +# Purge helper packages used only while building this stage. +apt-get purge -yq --auto-remove \ + file \ + gnupg2 \ + lsb-release + +# curl/wget are only needed during installation in this stage. +apt-get purge -yq --auto-remove \ + curl \ + wget + rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* truncate -s 0 /var/log/*log diff --git a/docker/prod/setup/prune-slim-runtime.sh b/docker/prod/setup/prune-slim-runtime.sh new file mode 100755 index 00000000000..c02fb01140e --- /dev/null +++ b/docker/prod/setup/prune-slim-runtime.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +set -euxo pipefail + +APP_PATH=${APP_PATH:-/app} + +# Remove source-only trees that are not needed for slim runtime images. +rm -rf \ + "$APP_PATH/spec" \ + "$APP_PATH/screenshots" \ + "$APP_PATH/lookbook" \ + "$APP_PATH/frontend" + +# Keep precompiled enterprise media in public/assets and remove duplicate source videos. +if [ -d "$APP_PATH/public/assets/enterprise" ]; then + rm -rf "$APP_PATH/app/assets/videos/enterprise" +fi + +# Source maps are useful during development, but unnecessary in slim runtime images. +find "$APP_PATH/public/assets" -type f -name '*.map' -delete + +# Lookbook source is removed above, so its compiled static assets are unnecessary too. +rm -rf "$APP_PATH/public/assets/lookbook" + +# Module test and documentation folders are not used at runtime. +find "$APP_PATH/modules" -mindepth 2 -maxdepth 2 -type d \ + \( -name spec -o -name test -o -name tests -o -name doc -o -name docs \) \ + -prune -exec rm -rf '{}' + + +# Remove leftover git metadata and common non-runtime folders from vendored git gems. +for gem_root in "$APP_PATH/vendor/bundle"/ruby/*/gems "$APP_PATH/vendor/bundle"/ruby/*/bundler/gems; do + [ -d "$gem_root" ] || continue + rm -rf "$gem_root"/*/.git + rm -rf "$gem_root"/*/{doc,docs,example,examples,benchmark,benchmarks} +done + +# Remove static/object files left by native builds. +find "$APP_PATH/vendor/bundle" -type f \( -name '*.a' -o -name '*.o' \) -delete diff --git a/docker/prod/supervisord b/docker/prod/supervisord index 086b2f44876..cce9f4b4e2f 100755 --- a/docker/prod/supervisord +++ b/docker/prod/supervisord @@ -57,16 +57,29 @@ install_plugins() { popd >/dev/null } +stop_memcached_daemon() { + /etc/init.d/memcached stop >/dev/null 2>&1 || true + if command -v pkill >/dev/null 2>&1; then + pkill -x memcached >/dev/null 2>&1 || true + fi + rm -f /var/run/memcached/memcached.pid +} + +start_memcached_daemon() { + stop_memcached_daemon + /etc/init.d/memcached start +} + migrate() { wait_for_postgres pushd $APP_PATH >/dev/null - /etc/init.d/memcached start + start_memcached_daemon echo "-----> Running migrations..." bundle exec rake db:migrate # run seed as app user so created attachments (and folder) belong to app, not root echo "-----> Seeding database..." su app -c 'bundle exec rake db:seed' - /etc/init.d/memcached stop + stop_memcached_daemon popd >/dev/null } @@ -141,6 +154,9 @@ fi echo "-----> Database setup finished." echo " On first installation, the default admin credentials are login: admin, password: admin" +# Ensure supervisord can manage memcached itself without a stale daemon keeping port 11211 busy. +stop_memcached_daemon + echo "-----> Launching supervisord..." erb -r uri $APP_PATH/docker/prod/supervisord.conf.erb > /etc/supervisor/supervisord.conf exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf -e ${SUPERVISORD_LOG_LEVEL} diff --git a/script/ci/docker_validate_image.sh b/script/ci/docker_validate_image.sh new file mode 100755 index 00000000000..47302be2095 --- /dev/null +++ b/script/ci/docker_validate_image.sh @@ -0,0 +1,323 @@ +#!/usr/bin/env bash + +set -euo pipefail +set -x + +usage() { + cat <<'USAGE' +Usage: script/ci/docker_validate_image.sh --image --target [--platform ] + +Validates target-specific runtime behavior of a built docker image. +USAGE +} + +log() { + printf '[docker-validate] %s\n' "$*" +} + +die() { + printf '[docker-validate] ERROR: %s\n' "$*" >&2 + exit 1 +} + +IMAGE="" +TARGET="" +PLATFORM="" +VALIDATION_PORT="${VALIDATION_PORT:-18080}" +VALIDATION_TIMEOUT_SECONDS="${VALIDATION_TIMEOUT_SECONDS:-300}" +VALIDATION_CONTAINER_NAME="" + +cleanup() { + if [[ -n "${VALIDATION_CONTAINER_NAME}" ]]; then + docker rm -f "${VALIDATION_CONTAINER_NAME}" >/dev/null 2>&1 || true + fi +} +trap cleanup EXIT + +while [[ $# -gt 0 ]]; do + case "$1" in + --image) + IMAGE="${2:-}" + shift 2 + ;; + --target) + TARGET="${2:-}" + shift 2 + ;; + --platform) + PLATFORM="${2:-}" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + usage + die "Unknown argument: $1" + ;; + esac +done + +[[ -n "${IMAGE}" ]] || { usage; die "--image is required"; } +[[ -n "${TARGET}" ]] || { usage; die "--target is required"; } + +command -v docker >/dev/null 2>&1 || die "docker is required" +command -v curl >/dev/null 2>&1 || die "curl is required" + +run_in_image_shell() { + local shell_script="$1" + docker run --rm --entrypoint sh "${IMAGE}" -lc "${shell_script}" +} + +validate_plugin_and_runtime_basics() { + run_in_image_shell "$(cat <<'SH' +set -eu + +check_present() { + if ! command -v -- "$1" >/dev/null 2>&1; then + echo "Expected command '$1' to be present" + exit 1 + fi +} + +check_file() { + [ -f "$1" ] || { + echo "Expected file '$1' to exist" + exit 1 + } +} + +check_present bin/rails +bin/rails --version >/dev/null + +[ "${BUNDLE_APP_CONFIG:-}" = "/app/.bundle" ] || { + echo "Expected BUNDLE_APP_CONFIG=/app/.bundle, got '${BUNDLE_APP_CONFIG:-}'" + exit 1 +} + +check_file /app/.bundle/config +grep -q 'BUNDLE_PATH: "vendor/bundle"' /app/.bundle/config || { + echo "Missing BUNDLE_PATH in /app/.bundle/config" + exit 1 +} +grep -q 'BUNDLE_DEPLOYMENT: "true"' /app/.bundle/config || { + echo "Missing BUNDLE_DEPLOYMENT in /app/.bundle/config" + exit 1 +} + +check_file /app/config/frontend_assets.manifest.json +ls /app/public/assets/frontend/*.js >/dev/null 2>&1 || { + echo "Expected compiled frontend javascript assets to exist" + exit 1 +} + +for plugin in budgets costs openproject-avatars openproject-documents \ + openproject-github_integration openproject-gitlab_integration openproject-meeting; do + grep -q -- "$plugin" /app/public/assets/frontend/*.js || { + echo "Expected plugin '${plugin}' to be present in compiled frontend assets" + exit 1 + } +done + +for plugin_dir in budgets costs avatars documents github_integration gitlab_integration meeting; do + [ -d "/app/modules/${plugin_dir}/frontend/module" ] || { + echo "Expected plugin frontend module directory '/app/modules/${plugin_dir}/frontend/module'" + exit 1 + } +done + +check_present convert +check_present tesseract +SH +)" +} + +validate_slim_pruning() { + run_in_image_shell "$(cat <<'SH' +set -eu + +check_absent_dir() { + [ ! -d "$1" ] || { + echo "Expected directory '$1' to be removed from slim image" + exit 1 + } +} + +check_present_dir() { + [ -d "$1" ] || { + echo "Expected directory '$1' to exist" + exit 1 + } +} + +check_absent_dir /app/frontend +check_absent_dir /app/spec +check_absent_dir /app/screenshots +check_absent_dir /app/lookbook +check_absent_dir /app/public/assets/lookbook +check_absent_dir /app/app/assets/videos/enterprise +check_present_dir /app/public/assets/enterprise + +if find /app/public/assets -type f -name '*.map' | grep -q .; then + echo "Expected source maps to be removed from slim runtime assets" + exit 1 +fi + +if find /app/modules -mindepth 2 -maxdepth 2 -type d \ + \( -name spec -o -name test -o -name tests -o -name doc -o -name docs \) | grep -q .; then + echo "Expected module test and doc folders to be removed from slim image" + exit 1 +fi +SH +)" +} + +validate_slim() { + validate_plugin_and_runtime_basics + validate_slim_pruning + + run_in_image_shell "$(cat <<'SH' +set -eu + +check_missing() { + if command -v -- "$1" >/dev/null 2>&1; then + echo "Expected command '$1' to be absent" + exit 1 + fi +} + +for tool in node npm gcc g++ make git svn hg; do + check_missing "$tool" +done +SH +)" +} + +validate_slim_bim() { + validate_plugin_and_runtime_basics + validate_slim_pruning + + run_in_image_shell "$(cat <<'SH' +set -eu + +check_present() { + if ! command -v -- "$1" >/dev/null 2>&1; then + echo "Expected command '$1' to be present" + exit 1 + fi +} + +check_missing() { + if command -v -- "$1" >/dev/null 2>&1; then + echo "Expected command '$1' to be absent" + exit 1 + fi +} + +[ "${OPENPROJECT_EDITION:-}" = "bim" ] || { + echo "Expected OPENPROJECT_EDITION=bim, got '${OPENPROJECT_EDITION:-}'" + exit 1 +} + +for tool in node npm IfcConvert COLLADA2GLTF xeokit-metadata; do + check_present "$tool" +done + +for tool in gcc g++ make git svn hg; do + check_missing "$tool" +done +SH +)" +} + +validate_all_in_one() { + VALIDATION_CONTAINER_NAME="openproject-validate-${RANDOM}-${RANDOM}" + local deadline=$((SECONDS + VALIDATION_TIMEOUT_SECONDS)) + local api_url="http://127.0.0.1:${VALIDATION_PORT}/api/v3" + + local docker_run_args=( + --name "${VALIDATION_CONTAINER_NAME}" + -d + -p "${VALIDATION_PORT}:80" + -e SUPERVISORD_LOG_LEVEL=debug + -e OPENPROJECT_LOGIN__REQUIRED=false + -e OPENPROJECT_HTTPS=false + ) + + if [[ -n "${PLATFORM}" ]]; then + docker_run_args+=(--platform "${PLATFORM}") + fi + + docker run "${docker_run_args[@]}" "${IMAGE}" + + while true; do + if curl --silent --fail "${api_url}"; then + break + fi + + if (( SECONDS >= deadline )); then + docker logs "${VALIDATION_CONTAINER_NAME}" --tail 400 || true + die "Timed out waiting for ${api_url}" + fi + + sleep 2 + done + + docker exec "${VALIDATION_CONTAINER_NAME}" sh -lc ' +set -eu +command -v -- gosu >/dev/null 2>&1 +gosu nobody true +[ -d /opt/hocuspocus ] +[ -x /usr/lib/postgresql/17/bin/psql ] +command -v -- node >/dev/null 2>&1 +command -v -- npm >/dev/null 2>&1 + +secret="$(tr "\0" "\n" < /proc/1/environ | sed -n "s/^OPENPROJECT_COLLABORATIVE__EDITING__HOCUSPOCUS__SECRET=//p" | head -n 1)" +[ -n "$secret" ] +case "$secret" in + (*[!A-Za-z0-9]*) + echo "Expected auto-generated hocuspocus secret to use YAML-safe alphanumeric characters only." + exit 1 + ;; +esac +ps -ef | grep -F "/opt/hocuspocus" | grep -v grep >/dev/null 2>&1 || { + echo "Expected bundled hocuspocus process to be running." + exit 1 +} +ps -ef | grep -F "/usr/bin/memcached" | grep -v grep >/dev/null 2>&1 || { + echo "Expected memcached process to be running." + exit 1 +} +' + + if docker logs "${VALIDATION_CONTAINER_NAME}" 2>&1 | grep -q "gave up: hocuspocus entered FATAL state"; then + docker logs "${VALIDATION_CONTAINER_NAME}" --tail 200 || true + die "Bundled hocuspocus failed to start in all-in-one image." + fi + + if docker logs "${VALIDATION_CONTAINER_NAME}" 2>&1 | grep -q "gave up: memcached entered FATAL state"; then + docker logs "${VALIDATION_CONTAINER_NAME}" --tail 200 || true + die "Bundled memcached failed to start in all-in-one image." + fi +} + +case "${TARGET}" in + slim) + log "Validating slim image (${IMAGE})" + validate_slim + ;; + slim-bim) + log "Validating slim-bim image (${IMAGE})" + validate_slim_bim + ;; + all-in-one) + log "Validating all-in-one image (${IMAGE})" + validate_all_in_one + ;; + *) + die "Unsupported target '${TARGET}'. Expected slim, slim-bim, or all-in-one." + ;; +esac + +log "Validation completed successfully for target '${TARGET}'." From 769d79c012eccd1f849a0823b7c98b3fc8518e5d Mon Sep 17 00:00:00 2001 From: OpenProject Actions CI Date: Wed, 11 Feb 2026 10:01:39 +0000 Subject: [PATCH 255/293] update locales from crowdin [ci skip] --- config/locales/crowdin/zh-TW.seeders.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/crowdin/zh-TW.seeders.yml b/config/locales/crowdin/zh-TW.seeders.yml index 99df46396ed..55632fa95c9 100644 --- a/config/locales/crowdin/zh-TW.seeders.yml +++ b/config/locales/crowdin/zh-TW.seeders.yml @@ -326,7 +326,7 @@ zh-TW: 1.*邀請新會員加入您的專案*: → 前往專案導覽中的【會員】({{opSetting:base_url}}/projects/your-scrum-project/members)。 2.*查看您的 Product backlogs 和 Sprint backlogs*: → 前往專案導覽中的 [Backlogs]({{opSetting:base_url}}/projects/your-scrum-project/backlogs) 。 - 3.*檢視您的任務板*: → 進入 [Backlogs]({{opSetting:base_url}}/projects/your-scrum-project/backlogs) → 點擊 Sprint 上的向右箭頭 → 選擇 [Task Board](##sprint:scrum_project__version___sprint_1)。 + 3.*檢視您的任務板*: → 進入 [Backlogs]({{opSetting:base_url}}/projects/your-scrum-project/backlogs) → 點擊 Sprint 上的向右箭頭 → 選擇 [Task Board](##sprint:scrum_project__version__sprint_1)。 4.*Create a new work package*: → 進入 [Work packages → Create]({{opSetting:base_url}}/projects/your-scrum-project/work_packages/new). 5.*Create and update a project plan*: → 前往專案導覽中的 [Project plan](##query:scrum_project__query__project_plan)。 6.*建立 Sprint wiki*: → 轉到 [Backlogs]({{opSetting:base_url}}/projects/your-scrum-project/backlogs),然後在 sprint 中從右邊的下拉式功能表開啟 sprint wiki。您可以根據需要編輯 [wiki 模板]({{opSetting:base_url}}/projects/your-scrum-project/wiki/) 。 From 2bc8fabc7ac6c21741f9c16c3afc291258e5a22b Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Wed, 11 Feb 2026 11:58:54 +0100 Subject: [PATCH 256/293] Optimise test to avoid duplication --- .../project_description_widget_spec.rb | 114 +++++++----------- 1 file changed, 46 insertions(+), 68 deletions(-) diff --git a/modules/overviews/spec/features/project_description_widget_spec.rb b/modules/overviews/spec/features/project_description_widget_spec.rb index dce073b2335..c4922aad122 100644 --- a/modules/overviews/spec/features/project_description_widget_spec.rb +++ b/modules/overviews/spec/features/project_description_widget_spec.rb @@ -62,80 +62,58 @@ RSpec.describe "Project description widget", :js, with_flag: { new_project_overv Pages::Projects::Show.new(portfolio) end + + shared_examples_for "adds a project description widget, and edits it correctly" do + before do + login_as user + + tested_page.visit! + end + + it do + expect(page).to have_current_path(path) + + # Edit the project description + # Find the editable description field + description_field = Turbo::TextEditorField.new(page, + "description", + selector:) + # Activate the field for editing + description_field.activate! + + # Set a new description + new_description = "This is a **test** project description with markdown formatting." + description_field.set_value(new_description) + + # Save the changes + description_field.save! + + tested_page.expect_and_dismiss_flash message: I18n.t("js.notice_successful_update") + + tested_page.visit! + wait_for_network_idle + expect(page).to have_content("This is a test project description with markdown formatting.") + + portfolio.reload + expect(portfolio.description).to include("This is a **test** project description") + end + end + + context "as a user with permission" do context "on the dashboard" do - before do - login_as user - - dashboard_page.visit! - end - - it "adds a project description widget, and edits it correctly" do - expect(page).to have_current_path(dashboard_project_overview_path(portfolio)) - - # Find the project description widget area - description_widget_area = Components::Grids::GridArea.new("[data-test-selector*='grid-widget-project_description']") - description_widget_area.expect_to_exist - - # Edit the project description within the widget - within description_widget_area.area do - # Find the editable description field - description_field = Turbo::TextEditorField.new(page, - "description", - selector: test_selector("op-overview-widget--project-description")) - # Activate the field for editing - description_field.activate! - - # Set a new description - new_description = "This is a **test** project description with markdown formatting." - description_field.set_value(new_description) - - # Save the changes - description_field.save! - end - - dashboard_page.expect_and_dismiss_flash message: I18n.t("js.notice_successful_update") - - dashboard_page.visit! - expect(page).to have_content("This is a test project description with markdown formatting.") - - portfolio.reload - expect(portfolio.description).to include("This is a **test** project description") + it_behaves_like "adds a project description widget, and edits it correctly" do + let(:tested_page) { dashboard_page } + let(:path) { dashboard_project_overview_path(portfolio) } + let(:selector) { test_selector("grid-widget-project_description") } end end context "on the overview" do - before do - login_as user - - overview_page.visit! - end - - it "opens the overview, and edits a project description correctly" do - expect(page).to have_current_path(project_overview_path(portfolio)) - - # Find the editable description field - description_field = Turbo::TextEditorField.new(page, - "description", - selector: test_selector("op-overview-widget--project-description")) - - # Activate the field for editing - description_field.activate! - - # Set a new description - new_description = "This is a **test** project description with markdown formatting." - description_field.set_value(new_description) - - # Save the changes - description_field.save! - - overview_page.expect_and_dismiss_flash message: I18n.t("js.notice_successful_update") - - overview_page.visit! - expect(page).to have_content("This is a test project description with markdown formatting.") - - portfolio.reload - expect(portfolio.description).to include("This is a **test** project description") + it_behaves_like "adds a project description widget, and edits it correctly" do + let(:tested_page) { overview_page } + let(:path) { project_overview_path(portfolio) } + let(:selector) { test_selector("op-overview-widget--project-description") } end end end From c82f493c9f2af05c32613d0287d5e969708d8d38 Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Wed, 11 Feb 2026 13:57:03 +0100 Subject: [PATCH 257/293] [71069] Use autocompleters in Admin/Backlogs page (#21841) * Replace selectPanel with autocompleters * Fix specs for updated autocompleter * Attempt to fix test * Replace ng.getComponent by ViewChild API --- .../op-autocompleter.component.html | 2 +- .../op-autocompleter.component.ts | 4 + .../admin/backlogs-settings.controller.ts | 139 ++++++++++-------- .../forms/dsl/autocompleter_input.rb | 7 +- .../admin/settings/backlogs_settings_form.rb | 66 ++++----- modules/backlogs/config/locales/en.yml | 4 - .../features/admin/backlogs_settings_spec.rb | 98 ++++-------- .../settings/backlogs_settings_form_spec.rb | 10 +- .../inputs/multi_select_list_spec.rb | 2 +- .../inputs/multi_version_select_list_spec.rb | 3 +- .../inputs/single_select_list_spec.rb | 6 +- .../inputs/single_version_select_list_spec.rb | 2 +- .../settings/custom_fields_form_spec.rb | 4 +- .../primerized/autocomplete_field.rb | 12 ++ 14 files changed, 170 insertions(+), 189 deletions(-) diff --git a/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component.html b/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component.html index da99c7cfe84..e48812d1d84 100644 --- a/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component.html +++ b/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component.html @@ -206,7 +206,7 @@ } @case (resource ==='subproject' || resource ==='version' || resource ==='status' || resource ==='default' || (!resource && !item.depth)) { {{ item.name }} diff --git a/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component.ts b/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component.ts index c6f0a33764a..8a9a0d59bde 100644 --- a/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component.ts +++ b/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component.ts @@ -334,6 +334,10 @@ export class OpAutocompleterComponent Backlogs page. + * Ensures that story types and task types are mutually exclusive. */ export default class BacklogsSettings extends Controller { static targets = ['storyTypes', 'taskType']; - declare readonly storyTypesTarget:SelectPanelElement; - declare readonly taskTypeTarget:SelectPanelElement; + declare readonly storyTypesTarget:HTMLElement; + declare readonly taskTypeTarget:HTMLElement; declare readonly hasStoryTypesTarget:boolean; declare readonly hasTaskTypeTarget:boolean; - private originalLabel?:string; + private isUpdating = false; - storyTypesTargetConnected(target:SelectPanelElement) { - target.addEventListener('itemActivated', this.onStoryTypesActivated); - - // this can be removed once implemented upstream: https://github.com/primer/view_components/pull/3825 - this.setDynamicLabel(this.storyTypesTarget); + storyTypesTargetConnected(target:HTMLElement) { + target.addEventListener('change', this.onStoryTypesChanged); } - storyTypesTargetDisconnected(target:SelectPanelElement) { - target.removeEventListener('itemActivated', this.onStoryTypesActivated); + storyTypesTargetDisconnected(target:HTMLElement) { + target.removeEventListener('change', this.onStoryTypesChanged); } - taskTypeTargetConnected(target:SelectPanelElement) { - target.addEventListener('itemActivated', this.onTaskTypeActivated); + taskTypeTargetConnected(target:HTMLElement) { + target.addEventListener('change', this.onTaskTypeChanged); } - taskTypeTargetDisconnected(target:SelectPanelElement) { - target.removeEventListener('itemActivated', this.onTaskTypeActivated); + taskTypeTargetDisconnected(target:HTMLElement) { + target.removeEventListener('change', this.onTaskTypeChanged); } - private onStoryTypesActivated = (_event:CustomEvent) => { - if (!this.hasTaskTypeTarget) return; - this.syncSelectPanels(this.storyTypesTarget, this.taskTypeTarget); + private onStoryTypesChanged = () => { + if (this.isUpdating || !this.hasTaskTypeTarget) return; - // this can be removed once implemented upstream: https://github.com/primer/view_components/pull/3825 - this.setDynamicLabel(this.storyTypesTarget); + this.syncDisabledOptions(this.storyTypesTarget, this.taskTypeTarget); }; - private onTaskTypeActivated = (_event:CustomEvent) => { - if (!this.hasStoryTypesTarget) return; - this.syncSelectPanels(this.taskTypeTarget, this.storyTypesTarget); + private onTaskTypeChanged = () => { + if (this.isUpdating || !this.hasStoryTypesTarget) return; + + this.syncDisabledOptions(this.taskTypeTarget, this.storyTypesTarget); }; /** - * Syncs two select panels - ensuring selections are mutually exclusive. + * Syncs disabled options between two autocompleters. + * Selected values in the source autocompleter will be disabled in the target. * - * @param source source select panel - * @param target target select panel + * @param sourceTarget The autocompleter whose selections should disable options in the target + * @param targetTarget The autocompleter whose options should be disabled */ - private syncSelectPanels(source:SelectPanelElement, target:SelectPanelElement) { - const sourceSelectedValues = new Set( - source.selectedItems - .map((item) => item.value) - .filter((value):value is string => value != null && value !== '') - ); + private syncDisabledOptions(sourceTarget:HTMLElement, targetTarget:HTMLElement) { + this.isUpdating = true; + try { + const sourceNgSelect = this.getNgSelectComponent(sourceTarget); + const targetNgSelect = this.getNgSelectComponent(targetTarget); - target.items.forEach((targetItem:SelectPanelItem) => { - const itemContent = targetItem.querySelector('.ActionListContent'); - const itemValue = itemContent?.dataset.value; - if (!itemValue) return; - - if (sourceSelectedValues.has(itemValue)) { - target.disableItem(targetItem); - target.uncheckItem(targetItem); - } else { - target.enableItem(targetItem); + if (!sourceNgSelect || !targetNgSelect) { + return; } - }); + + this.syncAutocompleters(sourceNgSelect, targetNgSelect); + } finally { + this.isUpdating = false; + } } - // this can be removed once implemented upstream: https://github.com/primer/view_components/pull/3825 - private setDynamicLabel(panel:SelectPanelElement) { - const invokerLabel = panel.invokerLabel!; - this.originalLabel ??= invokerLabel.textContent ?? ''; - const selectedLabels = Array.from(panel.querySelectorAll(`[${panel.ariaSelectionType}=true] .ActionListItem-label`)) - .map((label) => label.textContent?.trim() ?? '') - .join(', '); + /** + * Gets the NgSelectComponent instance from an op-autocompleter element. + */ + private getNgSelectComponent(target:HTMLElement):NgSelectComponent|null { + // Access the ng-select instance stored by op-autocompleter component + // eslint-disable-next-line @typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-member-access + return (target as any).ngSelectComponentInstance ?? null; + } - if (selectedLabels) { - const prefixSpan = document.createElement('span'); - prefixSpan.classList.add('color-fg-muted'); - const contentSpan = document.createElement('span'); - prefixSpan.textContent = `${panel.dynamicLabelPrefix} `; - contentSpan.textContent = selectedLabels; - invokerLabel.replaceChildren(prefixSpan, contentSpan); + /** + * Syncs two ng-select autocompleters - ensuring selections are mutually exclusive. + * + * @param source source autocompleter + * @param target target autocompleter + */ + private syncAutocompleters(source:NgSelectComponent, target:NgSelectComponent) { + const sourceSelectedIds = new Set( + source.selectedItems + .map((item) => item.value.id) + .filter((id) => id != null) + ); - if (panel.dynamicAriaLabelPrefix) { - panel.invokerElement?.setAttribute('aria-label', `${panel.dynamicAriaLabelPrefix} ${selectedLabels}`); + // Directly mutate the items array to ensure ng-select updates properly + let hasChanges = false; + target.itemsList.items.forEach((targetItem:NgOption) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const itemId = targetItem.value?.id; + + if (!itemId) return; + + const shouldBeDisabled = sourceSelectedIds.has(itemId); + if (targetItem.disabled !== shouldBeDisabled) { + targetItem.disabled = shouldBeDisabled; + hasChanges = true; } - } else { - invokerLabel.textContent = this.originalLabel; + }); + + // Force ng-select to re-render if we made changes + if (hasChanges) { + target.detectChanges(); } } } - diff --git a/lib/primer/open_project/forms/dsl/autocompleter_input.rb b/lib/primer/open_project/forms/dsl/autocompleter_input.rb index 8f70065067e..6f004f29d4d 100644 --- a/lib/primer/open_project/forms/dsl/autocompleter_input.rb +++ b/lib/primer/open_project/forms/dsl/autocompleter_input.rb @@ -8,18 +8,19 @@ module Primer attr_reader :name, :label, :autocomplete_options, :select_options, :wrapper_data_attributes class Option - attr_reader :label, :value, :selected, :classes, :group_by + attr_reader :label, :value, :selected, :classes, :group_by, :disabled - def initialize(label:, value:, classes: nil, selected: false, group_by: nil) + def initialize(label:, value:, classes: nil, selected: false, group_by: nil, disabled: false) @label = label @value = value @selected = selected @classes = classes @group_by = group_by + @disabled = disabled end def to_h - { id: value, name: label }.merge({ group_by:, classes: }.compact) + { id: value, name: label }.merge({ selected:, disabled:, group_by:, classes: }.compact) end end diff --git a/modules/backlogs/app/forms/admin/settings/backlogs_settings_form.rb b/modules/backlogs/app/forms/admin/settings/backlogs_settings_form.rb index 37915d60565..521a694523f 100644 --- a/modules/backlogs/app/forms/admin/settings/backlogs_settings_form.rb +++ b/modules/backlogs/app/forms/admin/settings/backlogs_settings_form.rb @@ -34,63 +34,59 @@ module Admin include ::Settings::FormHelper form do |f| - f.select_panel( + f.autocompleter( name: :story_types, label: I18n.t(:backlogs_story_type), - title: I18n.t(:label_select_types), caption: setting_caption(:plugin_openproject_backlogs, :story_types), - select_variant: :multiple, - fetch_strategy: :local, - dynamic_label: true, - dynamic_label_prefix: I18n.t(:label_selected_types), - data: { - admin__backlogs_settings_target: "storyTypes" + autocomplete_options: { + multiple: true, + closeOnSelect: false, + clearable: false, + decorated: true, + data: { + admin__backlogs_settings_target: "storyTypes", + test_selector: "story_type_autocomplete" + } } - ) do |select_menu| + ) do |list| available_types.each do |label, value| active = value.in?(Story.types) in_use = Task.type == value - select_menu.with_item( + list.option( label:, - content_arguments: { data: { value: } }, - active:, - disabled: in_use, - item_id: "type-#{value}", - label_arguments: { classes: "__hl_inline_type_#{value}" } + value:, + selected: active, + disabled: in_use ) end - - select_menu.with_footer(show_divider: true) do - render(Primer::Beta::Button.new(scheme: :primary, data: { action: "click:select-panel#hide" })) do - I18n.t(:button_apply) - end - end end - f.select_panel( + f.autocompleter( name: :task_type, label: I18n.t(:backlogs_task_type), - title: I18n.t(:label_select_type), caption: setting_caption(:plugin_openproject_backlogs, :task_type), - fetch_strategy: :local, - dynamic_label: true, - dynamic_label_prefix: I18n.t(:label_selected_type), - data: { - admin__backlogs_settings_target: "taskType" + input_width: :small, + autocomplete_options: { + multiple: false, + closeOnSelect: true, + clearable: false, + decorated: true, + data: { + admin__backlogs_settings_target: "taskType", + test_selector: "task_type_autocomplete" + } } - ) do |select_menu| + ) do |list| available_types.each do |label, value| active = Task.type == value in_use = value.in?(Story.types) - select_menu.with_item( + list.option( label:, - content_arguments: { data: { value: } }, - active:, - disabled: in_use, - item_id: "type-#{value}", - label_arguments: { classes: "__hl_inline_type_#{value}" } + value:, + selected: active, + disabled: in_use ) end end diff --git a/modules/backlogs/config/locales/en.yml b/modules/backlogs/config/locales/en.yml index dbeee1707a2..c36d4b44210 100644 --- a/modules/backlogs/config/locales/en.yml +++ b/modules/backlogs/config/locales/en.yml @@ -142,10 +142,6 @@ en: label_column_in_backlog: "Column in backlog" label_points_burn_down: "Down" label_points_burn_up: "Up" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "Sprint Impediments" label_task_board: "Task board" diff --git a/modules/backlogs/spec/features/admin/backlogs_settings_spec.rb b/modules/backlogs/spec/features/admin/backlogs_settings_spec.rb index ac74c2e99f3..f0e427965ab 100644 --- a/modules/backlogs/spec/features/admin/backlogs_settings_spec.rb +++ b/modules/backlogs/spec/features/admin/backlogs_settings_spec.rb @@ -36,6 +36,9 @@ RSpec.describe "Backlogs Admin Settings", :js do let!(:type3) { create(:type_task, position: 3) } let!(:type4) { create(:type_milestone, position: 4) } + let(:story_autocompleter) { FormFields::Primerized::AutocompleteField.new("story_types", selector: "[data-test-selector='story_type_autocomplete']") } + let(:task_autocompleter) { FormFields::Primerized::AutocompleteField.new("story_types", selector: "[data-test-selector='task_type_autocomplete']") } + let(:current_user) { create(:admin) } before do @@ -47,103 +50,56 @@ RSpec.describe "Backlogs Admin Settings", :js do scenario "updating story types" do expect(page).to have_heading "Backlogs" - click_on accessible_description: "Story types" - - within_dialog "Select types" do - within(:role, :listbox, accessible_name: "Select types options") do - page.find(:role, :option, accessible_name: "FEATURE").click - page.find(:role, :option, accessible_name: "STORY").click - end - - click_on "Apply" - end - - expect(page).to have_button accessible_description: "Story types", text: "Selected types: Story, Feature" + story_autocompleter.select_option "Feature", "Story" click_on "Save" expect_and_dismiss_flash type: :success, message: "Successful update." - expect(page).to have_button accessible_description: "Story types", text: "Selected types: Story, Feature" + story_autocompleter.expect_selected "Feature", "Story" end - scenario "filtering story types" do - expect(page).to have_heading "Backlogs" - - click_on accessible_description: "Story types" - - within_dialog "Select types" do - within(:role, :listbox, accessible_name: "Select types options") do - expect(page).to have_selector :role, :option, count: 4, visible: :visible - end - fill_in "Filter", with: "f" - - within(:role, :listbox, accessible_name: "Select types options") do - expect(page).to have_selector :role, :option, count: 1, visible: :visible - end - - click_on "Apply" - end - end scenario "updating task type" do expect(page).to have_heading "Backlogs" - click_on accessible_description: "Task type" - - within_dialog "Select a type" do - within(:role, :listbox, accessible_name: "Select a type options") do - page.find(:role, :option, accessible_name: "TASK").click - end - end - - expect(page).to have_button accessible_description: "Task type", text: "Selected type: Task" + task_autocompleter.select_option "Task" click_on "Save" expect_and_dismiss_flash type: :success, message: "Successful update." - expect(page).to have_button accessible_description: "Task type", text: "Selected type: Task" + task_autocompleter.expect_selected "Task" end scenario "ensuring the same type is not selected as story and task type" do expect(page).to have_heading "Backlogs" - click_on accessible_description: "Story types" + wait_for_network_idle - within_dialog "Select types" do - within(:role, :listbox, accessible_name: "Select types options") do - expect(page).to have_selector(:role, :option, accessible_name: "STORY") + wait_for_autocompleter_options_to_be_loaded + story_autocompleter.expect_blank + task_autocompleter.expect_blank - page.find(:role, :option, accessible_name: "FEATURE").click - end + # Select a value in the story autocompleter... + story_autocompleter.select_option "Feature" + story_autocompleter.expect_selected "Feature" + story_autocompleter.expect_not_disabled "Story" + story_autocompleter.close_autocompleter - click_on "Apply" - end + # ... which is then disabled in the task autocompleter. + task_autocompleter.open_options + task_autocompleter.expect_disabled "Feature" - expect(page).to have_button accessible_description: "Story types", text: "Selected types: Feature" + # Other way around: Select a value in the task automcompleter... + task_autocompleter.select_option "Story" + task_autocompleter.expect_selected "Story" + task_autocompleter.close_autocompleter - click_on accessible_description: "Task type" - - within_dialog "Select a type" do - within(:role, :listbox, accessible_name: "Select a type options") do - expect(page).to have_selector(:role, :option, accessible_name: "FEATURE", aria: { disabled: true }) - - page.find(:role, :option, accessible_name: "STORY").click - end - end - - expect(page).to have_button accessible_description: "Task type", text: "Selected type: Story" - - click_on accessible_description: "Story types" - - within_dialog "Select types" do - within(:role, :listbox, accessible_name: "Select types options") do - expect(page).to have_selector(:role, :option, accessible_name: "STORY", aria: { disabled: true }) - end - - click_on "Apply" - end + # ... which will be disabled in the story autocompleter + story_autocompleter.open_options + story_autocompleter.expect_disabled "Story" + story_autocompleter.expect_selected "Feature" end scenario "updating points burn direction" do diff --git a/modules/backlogs/spec/forms/admin/settings/backlogs_settings_form_spec.rb b/modules/backlogs/spec/forms/admin/settings/backlogs_settings_form_spec.rb index ca90dd8d610..244ef87a0d5 100644 --- a/modules/backlogs/spec/forms/admin/settings/backlogs_settings_form_spec.rb +++ b/modules/backlogs/spec/forms/admin/settings/backlogs_settings_form_spec.rb @@ -41,11 +41,13 @@ RSpec.describe Admin::Settings::BacklogsSettingsForm, type: :forms do end it "renders", :aggregate_failures do - expect(rendered_form).to have_element "select-panel", "data-dynamic-label-prefix": "Selected types" - expect(rendered_form).to have_field "settings[story_types][]", type: :hidden, multiple: true + expect(rendered_form).to have_element "opce-autocompleter", "data-label-for-id": "\"settings_story_types\"" do |autocompleter| + expect(autocompleter["data-multiple"]).to be_json_eql(%{true}) + end - expect(rendered_form).to have_element "select-panel", "data-dynamic-label-prefix": "Selected type" - expect(rendered_form).to have_field "settings[task_type]", type: :hidden + expect(rendered_form).to have_element "opce-autocompleter", "data-label-for-id": "\"settings_task_type\"" do |autocompleter| + expect(autocompleter["data-multiple"]).to be_json_eql(%{false}) + end expect(rendered_form).to have_field "Template for sprint wiki page", type: :text do |field| expect(field["name"]).to eq "settings[wiki_template]" diff --git a/spec/forms/custom_fields/inputs/multi_select_list_spec.rb b/spec/forms/custom_fields/inputs/multi_select_list_spec.rb index dd7a6cff0e5..2d0feee7c65 100644 --- a/spec/forms/custom_fields/inputs/multi_select_list_spec.rb +++ b/spec/forms/custom_fields/inputs/multi_select_list_spec.rb @@ -48,7 +48,7 @@ RSpec.describe CustomFields::Inputs::MultiSelectList, type: :forms do it "sets correct autocompleter inputs" do expect(autocompleter["data-items"]).to have_json_size(4) expect(autocompleter["data-model"]).to have_json_size(2) - expect(autocompleter["data-model"]).to be_json_eql(%{[{"name": "tre"}, {"name": "quattro"}]}) + expect(autocompleter["data-model"]).to be_json_eql(%{[{"disabled": false, "name": "tre", "selected": true}, {"disabled": false, "name": "quattro", "selected": true}]}) end end end diff --git a/spec/forms/custom_fields/inputs/multi_version_select_list_spec.rb b/spec/forms/custom_fields/inputs/multi_version_select_list_spec.rb index 36f7dfe86b0..fb4fbc889b8 100644 --- a/spec/forms/custom_fields/inputs/multi_version_select_list_spec.rb +++ b/spec/forms/custom_fields/inputs/multi_version_select_list_spec.rb @@ -42,7 +42,8 @@ RSpec.describe CustomFields::Inputs::MultiVersionSelectList, type: :forms do it "sets correct autocompleter inputs" do expect(autocompleter["data-items"]).to have_json_size(5) expect(autocompleter["data-model"]).to have_json_size(2) - expect(autocompleter["data-model"]).to be_json_eql(value.map { it.slice(:name) }.to_json).excluding("group_by") + expect(autocompleter["data-model"]) + .to be_json_eql(value.map { it.slice(:name) }.to_json).excluding("group_by", "selected", "disabled") end end end diff --git a/spec/forms/custom_fields/inputs/single_select_list_spec.rb b/spec/forms/custom_fields/inputs/single_select_list_spec.rb index e6775e57286..bf01b25d304 100644 --- a/spec/forms/custom_fields/inputs/single_select_list_spec.rb +++ b/spec/forms/custom_fields/inputs/single_select_list_spec.rb @@ -40,7 +40,7 @@ RSpec.describe CustomFields::Inputs::SingleSelectList, type: :forms do it_behaves_like "rendering autocompleter", "List field" do it "sets correct autocompleter inputs" do expect(autocompleter["data-items"]).to have_json_size(3) - expect(autocompleter["data-model"]).to be_json_eql(%{{"name": "eins"}}) + expect(autocompleter["data-model"]).to be_json_eql(%{{"disabled": false, "name": "eins", "selected": true}}) end end @@ -63,7 +63,7 @@ RSpec.describe CustomFields::Inputs::SingleSelectList, type: :forms do # [1] CustomFields::Inputs::SingleSelectList#list_items describe "with an option selected" do it "pre-selects the selected value" do - expect(autocompleter["data-model"]).to be_json_eql(%{{"name": "drei"}}) + expect(autocompleter["data-model"]).to be_json_eql(%{{"disabled": false, "name": "drei", "selected": true}}) end end @@ -71,7 +71,7 @@ RSpec.describe CustomFields::Inputs::SingleSelectList, type: :forms do let(:value) { nil } it "pre-selects the default value" do - expect(autocompleter["data-model"]).to be_json_eql(%{{"name": "zwei"}}) + expect(autocompleter["data-model"]).to be_json_eql(%{{"disabled": false, "name": "zwei", "selected": true}}) end end end diff --git a/spec/forms/custom_fields/inputs/single_version_select_list_spec.rb b/spec/forms/custom_fields/inputs/single_version_select_list_spec.rb index 999e9aafe58..f5db16078b9 100644 --- a/spec/forms/custom_fields/inputs/single_version_select_list_spec.rb +++ b/spec/forms/custom_fields/inputs/single_version_select_list_spec.rb @@ -40,7 +40,7 @@ RSpec.describe CustomFields::Inputs::SingleVersionSelectList, type: :forms do it_behaves_like "rendering autocompleter", "Version field" do it "sets correct autocompleter inputs" do expect(autocompleter["data-items"]).to have_json_size(1) - expect(autocompleter["data-model"]).to be_json_eql(%{{"name":"Version 26"}}).excluding("group_by") + expect(autocompleter["data-model"]).to be_json_eql(%{{"name": "Version 26"}}).excluding("group_by", "selected", "disabled") end end end diff --git a/spec/forms/projects/settings/custom_fields_form_spec.rb b/spec/forms/projects/settings/custom_fields_form_spec.rb index dae7f3bf554..eddd558ec48 100644 --- a/spec/forms/projects/settings/custom_fields_form_spec.rb +++ b/spec/forms/projects/settings/custom_fields_form_spec.rb @@ -121,7 +121,7 @@ RSpec.describe Projects::Settings::CustomFieldsForm, expect(page).to have_element "opce-autocompleter", "data-label-for-id": "\"#{label_id}\"" do |autocompleter| expect(autocompleter["data-multiple"]).to be_json_eql(%{false}) expect(autocompleter["data-items"]).to have_json_size(3) - expect(autocompleter["data-model"]).to be_json_eql(%{{"name": "eins"}}) + expect(autocompleter["data-model"]).to be_json_eql(%{{"disabled": false, "name": "eins", "selected": true}}) end end @@ -133,7 +133,7 @@ RSpec.describe Projects::Settings::CustomFieldsForm, expect(autocompleter["data-multiple"]).to be_json_eql(%{true}) expect(autocompleter["data-items"]).to have_json_size(4) expect(autocompleter["data-model"]).to have_json_size(2) - expect(autocompleter["data-model"]).to be_json_eql(%{[{"name": "tre"}, {"name": "quattro"}]}) + expect(autocompleter["data-model"]).to be_json_eql(%{[{"disabled": false, "name": "tre", "selected": true}, {"disabled": false, "name": "quattro", "selected": true}]}) end end diff --git a/spec/support/form_fields/primerized/autocomplete_field.rb b/spec/support/form_fields/primerized/autocomplete_field.rb index 39891f25e9f..86f77d8d1b8 100644 --- a/spec/support/form_fields/primerized/autocomplete_field.rb +++ b/spec/support/form_fields/primerized/autocomplete_field.rb @@ -68,6 +68,18 @@ module FormFields end end + def expect_disabled(*values) + values.each do |val| + expect(page).to have_css(".ng-option.ng-option-disabled", text: val) + end + end + + def expect_not_disabled(*values) + values.each do |val| + expect(page).to have_no_css(".ng-option.ng-option-disabled", text: val, wait: 1) + end + end + def expect_blank expect(field_container).to have_css(".ng-value", count: 0) end From 28adc2b43a03b5e38c11461f186eebf36b140ee5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tizian=20R=C3=B6=C3=9Fler?= Date: Tue, 10 Feb 2026 14:52:31 +0100 Subject: [PATCH 258/293] add configuration instructions for sendmail to docs --- .../configuration/outbound-emails/README.md | 38 +++++++++++++++--- .../outbound-emails/sendmail.png | Bin 0 -> 18998 bytes 2 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 docs/installation-and-operations/configuration/outbound-emails/sendmail.png diff --git a/docs/installation-and-operations/configuration/outbound-emails/README.md b/docs/installation-and-operations/configuration/outbound-emails/README.md index 15ca4b7005e..e601ee9d9ec 100644 --- a/docs/installation-and-operations/configuration/outbound-emails/README.md +++ b/docs/installation-and-operations/configuration/outbound-emails/README.md @@ -5,10 +5,11 @@ sidebar_navigation: --- # Configuring outbound emails +## SMTP In this guide we will describe how to configure outbound emails using an external SMTP server. -## Requirements +### Requirements You will need to have SMTP settings ready. Those can either be from a company SMTP server, a Gmail account, or a public provider such as [SendGrid](https://www.sendgrid.com/). @@ -24,7 +25,7 @@ You can adjust those settings for other SMTP providers, such as Gmail, Mandrill, etc. Please refer to the documentation of the corresponding provider to see what values should be used. -## Configuration through the Admin UI +### Configuration through the Admin UI OpenProject allows you to configure your SMTP settings through the administration UI. Using the default admin account created when you first installed OpenProject, go to Administration > Emails and notifications. @@ -32,7 +33,7 @@ At the bottom of this screen, you will find the following configuration form. ![smtp](smtp.png) -## SMTP Options +### SMTP Options These are the options that are available. Please see the [Configuration guide](../) and [Environment variables guide](../environment) on how to set these values from the command line. @@ -49,7 +50,7 @@ These are the options that are available. Please see the [Configuration guide](. | OpenSSL verify mode | smtp_openssl_verify_mode | `OPENPROJECT_SMTP__OPENSSL__VERIFY__MODE` | Define how the SMTP server certificate is validated. Make sure you don't just disable verification here unless both, OpenProject and SMTP servers are on a private network. Possible values: `none`, `peer`, `client_once` or `fail_if_no_peer_cert`.
    Note: This setting can only be set through ENV/settings | | SMTP Timeout | smtp_timeout | `OPENPROJECT_SMTP__TIMEOUT` | This optional setting allows you to specify the number of seconds to wait for SMTP connections to be opened and read.
    If the value is set too low, a `Net::OpenTimeout` or `Net::ReadTimeout` might be raised. | -## Package-based installation (DEB/RPM) +### Package-based installation (DEB/RPM) If you installed OpenProject with the package-based installation, you can configure the above settings using the config:set helper. Please note that this will disable the settings in the administration UI. @@ -64,7 +65,7 @@ openproject config:set OPENPROJECT_SMTP__USER__NAME="apikey" openproject config:set OPENPROJECT_SMTP__PASSWORD="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" ``` -## Docker installation +### Docker installation If you installed OpenProject with Docker, here is how you would enable outbound emails through the use of the SMTP environment variables (with SendGrid, the `SMTP_USER_NAME` is always `apikey`. Just replace `SMTP_PASSWORD` with the API key you've generated and you should be good to go). Please note that this will disable the settings in the administration UI. @@ -81,3 +82,30 @@ docker run -d \ -e OPENPROJECT_SMTP__PASSWORD="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" \ ... ``` +## Sendmail + +### Requirements + +You need to have Sendmail configured on your server. +For information about how to configure Sendmail, please refer to the [Sendmail docs](https://www.sendmail.org/~ca/email/doc8.12/cf/m4/index.html) + + +### Configuration through the Admin UI + +OpenProject allows you to configure your Sendmail through the administration UI. Using the default admin account created when you first installed OpenProject, go to Administration > Emails and notifications. +Here, you need to change the `Email delivery method` to `sendmail`. + +![sendmail](sendmail.png) + +If you want to override the path where Sendmail is installed or change the command-line arguments, you can’t do this through the web frontend. +In this case, please use the variable shown in the next section to configure the path or arguments. + +### Sendmail Options + +These are the options that are available. Please see the [Configuration guide](../) and [Environment variables guide](../environment) on how to set these values from the command line. + +| Option | Setting | ENV name | Description | +| -------------------------- | ------------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------- | +| Email delivery method | email_delivery_method | `OPENPROJECT_EMAIL__DELIVERY__METHOD` | email delivery method to be used (smtp, sendmail) | +| Sendmail location | sendmail_location | `OPENPROJECT_SENDMAIL__LOCATION` | Location of sendmail to call if it is configured as outgoing email setup. Default value: `/usr/sbin/sendmail` | +| Sendmail arguments | sendmail_arguments | `OPENPROJECT_SENDMAIL__ARGUMENTS` | Arguments to call sendmail with in case it is configured as outgoing email setup. Default value: `-i` | diff --git a/docs/installation-and-operations/configuration/outbound-emails/sendmail.png b/docs/installation-and-operations/configuration/outbound-emails/sendmail.png new file mode 100644 index 0000000000000000000000000000000000000000..254ddc14e2c59a53803d3ee0234e467baf526500 GIT binary patch literal 18998 zcmc$_bx>Pv-!4jxK9mAap-`L$iWDziye;nTgcL9C8l(o4;u0uOpv8lP;t&Xd7T2P| z-Gf7rAUTWoeZMoaXU^>T_UyCI`D;NYS@*rx`pNaXu4_eVYbudEpnX6&S;Fj25w}DUoa*qxoAW*qdk$0!#?Av~g_e%nkM&z#x9=MMG;>B< zG&V|(Ply=Kg3#uTjda9~0?%LS^XT7&28Djb?t+L~3zv)M2bUYX2EV0=3(Y{pQQ|_U zv`UJKT)-Lx`)M@X|2m#0NZ%aazTdtD99|OJklp-i`VZvh@aOjA9pLbes*CaF-|Ckq zH;3={D(~MMdE@^N|CBsa+M?;<%ugS5+EHqjY^Z~SL&BJhS0*ida$BgkG|C9>GTX2h zJBUP=&*!N^bK=#qB*sAbv4}TEq(~MPmPC%KZ=xYZ0mFH^??|w~Bx2vszo5IXrJ+b%km`q@RkQ(~&&i5f>O)DKPvM8OzQh5xE)EcQ; zTg#_?n2J&|(%4io*nJ{%?m6kh-rBq^n$L0)bB#$C2BG9*e?n%r5T55g(F{{cVyAHm z+$T&j>=eATV`Zid-%P1#XeRUb_Sg9>dU`@LB@{!R>iVDjTJ07JLG!+-28&&&i#jHS zT-XLx`k~rRizL}72nh*QlG4R(DxAzw1;KXHXK_{j3;4Io2^X(E(s8hTHGF)W%jrD!=uwGz4m@^Xx-7xQ|1e< zA6e&9wKue5knprx8ua2bKM6cKaXDN`iOZInuDxi9uh9GW;UgBkFStL3(2!i3{Nydb zP&oyY9nLH>8qttQ)^$x12reszj!IOm^Co0-qg{Kd4bt_=BlsYoy60NOtY1Owbe+y8 z!(V4FqzkTah1Wcr_V^J3q>rjaF{rFkUAaYDt#()9pH?|U7rE42_^0Tv`&Dg3n+9rk z9}tr?F8Nq*mCDxc$$V!GFYO(d$eq7~hhl0=El?uk5*;0i2&w{=Ox@6wm1_H2gJTYx zUfB{5=C!5Na*Naj%M?}>fBCipJ^QiO5v3>49;j|%i2)x+tDMSn6@Kz+*5NL<&|!Wr zZ0K?YqkT5yG$J-LaHFVDST0LJ@N}ZPDv_)lGN01WPQ0EPsZr4lh z^i)G#oz=zE)L5AcRONB8p)#_nQZ)bCAw00&xlCvym1E^CPzM(DRA%Zs6DcuyPTUOj z*U}-$2M;z~=k}X3d80DDu~d9uZn9n|-*|09V?x5vRmAmj;Wh58djxCdD-!BgAggd_ zQ~u$D->#&2g>!c_&2T#+^4Gn?T9-vQHAZniJ~pM;?8H}O;tI1r zd`5KJA9U0BZQ96&K6*x>`k~ucW9S3la96WjXK%}A@6fRk{Od~jPSJiVP)|(|49F4x z`DSj8?W!r;DX$heQLbfPK{djl-PzD*Fl+6-fnl-~xr(6v0$$0}GUGZ0MNw~V{P$f3 zGnKPRuZ6Bllwqb!s7m-Ft8tYymiCQSq;-m#@=@sJ%5j4lv{%XJ4_n5)<_EgO*b)YD z57X;1we1z-wkQ1i_Yx1$rJt{(a|O|hyI)bzaQ->Jd&XqnM%u1T6Lm;TFe zj)yc6qv|nK6i6TFhTf*25$*%;3`TW7o*eUVvqYb(unF^`ShqlV*Yt%mSAe`La3UbGsuB3On0JTIi5i?LHPtI>aT zJFFLB#(AtVcn05-W-Bgn6!v@*_mz@S>Nqtt(@VS?m1KX(htcW9?)9I2*O)%pmUdrR zD{CYt-$EDX%x}vnNs}pb(p9XSX3sa?|E?a%B53YDiNWAVa(2l~3Q)EL9)vs#? zZAwZwubheAppkB}frs)cKf!oo9;MBWGD$m?MCHK9nppbbf}Lv_!M&XtFU>;YROAOR zO4HGfT}d#)dOUB<4%eI2=x(ys^ENU1&f~82Cj5TIQx46svc-Z+rr1m=uk)!GrMN8R zpDNh{ToFkhT@Cw|gV1PxK^%*;B(fI)6lb-2oT0}nX>F!7$_b8z0~URYUw2mKpO`nW zx|^#Zr{zf#LM-Fn=Z+F#PpClwuVY1ldnyB!kA@V16fNJp{o5#J0)xVcG?Nesh-ppz z)YRwLD~Hlqfg--`r@$SL2Q!ViKZPl*GKuslPbKh`?oMTVY79o7CPt4n1p{#gI}Mz} zokH4Xv?g715h6J!t1h3-+ACptR~X-@u281;pHWQgOIFWSNXdg6wit z%&hWl>yO|@l_T$}CGi2z>TB#jIAfSnv2j9?nat=ddaPZX)sWbGR(WIp(SH;ZL&>Z* zc2E8HPQHGkzlo(Fu1M)RTZ?2P-V>{o7H`5d-10ip9!9y3yY?d~Up(=Sa2;8fo zQ(kJCD<>_8x1$SGVr>6F7D6w{w*Jc}8S9lMm_GTlg4=!>6e31;*iBJ>ZNl(HBKWfLdY)jC%4>NJ3V;82!krZih(Zy z-^u_em95|xbm=`Cob88du6D?cHNaIHnrsVpm!EBk5dCN-r?w)kXnCL%@ObPYd7T>^}{(~)BOB$W1I;ZgpcTWz5rgYv==zYDBzu1PBPl(gz|V@G2|eK>qY+ZbbRB8>;hhP+TeN0+ zeFpPNtXEV&6Hm(zqo)!1DB@&ncI6pOu0ZTOQ+sicR47dttD3|$R<+L0N|>jbmSf(X zvsc#BHc{_!l`a%&Rv)xuC$8F^#0Dw1-+>ys~9U9%u7n>2M6AGotXIDD2Q?gao}IHjF>e z3lNkd_U98&WTf)$)@%0mxFS?f!$wFn+oVnw{3g>lwh!L_FF14xv8kwHkj|_7mXyMm z`wb-~D44jL9v0Tn#|6sSZ?VYCvvf*pv>FCLY|M|U7yJ#3d!KzG393Xbc*^(!9k$yC>=HKqW4qq3%(n)*zY zQ8g+21Ve=>WHrfG;Z;#&r{V$es=VY)endoMv^D62RLfgRZ5#DodB-^XF?IiF&E@lP z9vc|DIW<@F+M(uyFr+Vj4uFu+4;XI^^7bY5Jff4Xud{UqPje_u2xK7h3fL-UDJiI^ z;7*HG@1E@S^HN_SBZh#Mu#u`p+e9x-rJwWm709yUHQ%9HGI;g+x6;@koO0Tiss3n} z_0GudPO}jMEj+aWc2~{H7T8$sm41ENE*+dl^ z?blaps%$tX7HgNh(vkJKt1e}Qklvlpw=<1u)2AIOtQ5r&nCp*90vS*6LxcnbErnM5 zb2~#oQ6bnDXWdQz*D!nb;=eP!hRNH+5u^A1JqGKXh5UQqUHJDY{y!Xi7ov##X;#+p z!YqQiw`A_g-MN!-?4Zk5r%gbBR{<^r9DSFJ1LCAdG52}j0f0pylvg$|q~lF4yYKI} zcYVI!dM7(xDwA6A&O5x|_14Xx{=YM?fB12je~*xmidX04WJgj^I&k}wCJ5374n^-G z&xtAHP?}@uK2LZ}aC1EpOuxmGGVGvE?$eKvk^E2!Mi=xr4H%9 zlTDQ6vfb=&o9;xA6b5z~+SlKY8HYL8LKV*+%3ykA%lge~L5(4~ZhMRm-r@5ke708l zGmHQhf{A+wZQfuAfAQVT&1dRQm-0D^hK7b@d*0|=>R&&UzBsY3tT7jU6F^M=3;w!Lpw?Z~mi z1A-q8fFPt0dXF+wp$M;Q1PSf@@rhwcSXfw0_KSv>35lNc2;aRco_}6GfE@2~WMqjz z;MK*!ou~`3!EDsq=^sfv7Ns`g4$-c199o$vls$$ke`#IVD*F4^n_e%qtC?61NSzZ? z@rs1DTIuQO-8(C$mkFvjuCSt_qH-81D6>EYy1V~;EaSib-E|gmIFKY0jANDyFVZ?& zAIzqsqf@(j9rl1tBVE{j;2WKo>u8xhc5953l+@17&bBut_;@1$KGWWAyE&Zi>gp=U z?KEDB0a%(Er_`#ds)zAZ{1%s6V9)p&Pb=0{yi1+Q%{vGtBR8X6jYBs|8xsP-Eu z-6Vd(d+@@OhnX7{yzY9M6`UdLD_iepf8kq}Ussk<3_1FhI8+&We~5F?2gNxzoL?@_Hi@DceNq~vqt<{-Z<>fbI&(h zV`_K2><6i(J>Sks)JSP%1s-oyNH}#4%!-PR^PSJ3a8{#V4`S#}5ip@UP*?bOuAC>JN}|MZ-+5;uHbZh_sN1wSa%$}lhRhQTu>=NbaAaSfqn^eUyO7evbha0*`By)m1PhK6`I;r+52=jXHIoj^VXW547( z8D9D<4(D?l!MR$D@^x|*cXxMXWMm>p861c6BIR!fxWNp|OcCCjO-)U;z~gnEtn5zW zD1q9J$H`v5a2RGyGidbPS?kY0`5(OQahs`U?)BaH^NJmJyeSlP=64PJ{)bdh z?G{0h3&8G;m(4%RrQg!({3Z!SrFTvBd_gDc+l-%+FpJ zCnj#gkf61c{tSy4nZH`g!M#)wWDF}C@%`iD1zP315u}g}@*#+gJ!T9B6_6uz_)0$w z6dbihaBqrzx7B=!l2~J3+Ugrp#tFA>aPM4Gz|)NB`T)%LTUU87UCZsF#qea9&P(uk*iEkMztD2a&CQL*+qIQ|89-9A>EbKg?O+_Q+|I=CXmr8dcn{B-O zIFF@V+49QD==WE5oSd9!=n`om{E&CPX{FcDPqQSO|+@ST$yKpX}I94TiV93VNa zM8!!;CABLRoG{T3_ObColWM7aE@Q=GiEL^%f4wGr866$XXVLVm91~504-rHKUl{(1 zbWNCP@G7AYoZ-ri;s-bD=U6<8fxYHI2tO(xlp zpry9RRDN)ZQAugl+o7BX&GaJH%1xutzoiHkFwl+d1gU_^ZOvGL-u}M6YZF+7O5cOV z(_Ok3HfKy;_TA6Jyg6qwsDF)+Q7wQ{eJ)V?a8 zX;Pa0c%8>SlyE3_&&I1hhJ?93NR-x9K*tsEc;2=T@Oi<%iCek8#{biV)(3rDomV)* zE2r|6!JoU(GmUdT;QrU7dQATjQBn&{R_X{ z83sw;E}@z6@$uik<&2c@Dz0!p-aPQZ?W19|7!L(ke@22&yvbwY{*Q0 z8}RdE|LQ%Tf`pt>E;w0RS`Ou_rP}qU+YWp?UugX(Sh={q&cUxmN0)II&I~j=rPxca zo%}#%!(W+F0fIa{rJ?xiEyynyq!@kBD@4<)eS&x8ra5jze?5G7eA1kMJ=5$Zwn#i> zI<4#+-~N<^#jZc=;@NIg?%YU`<<-#>z5(#hv)11c5z?`I;09B%6410EQ20!KzU>Av zG4Yo>0bdyhi99NisBwauG+uXT>^U0Dj3%VFDtd5p?BM~HLE1IvIQ#JehI#KH>hds3 zKI{p<1-)#D`C(Pw=LeKr%9rkbezLyX6J=&~zCdOI4d@I#QtM)-rKM#uhJ>8@?#*3w zGKD^iw6wS9P_aOs%2DAL*1o4D3_S*F4$sh}oJ2q6Y;lQf&6cS8*$0h``o`A^nrl-%X}%4;^>P-S0gT!S!g=MoCdl=hgRp5=-BTaP^kT;b`%R!6I0Rx9p1 zBXnI(pU~&Nc?dmR?P1l*Qs?cqacbNy4U zlqrpI!KEAhbnIgoM5OF}3k!Hn$6WrfmLQQ%t6zX5nyhmJN0PG*7i!Jdxy_#+txJlF zcSl(PP4?vE1Zb*$zP`J&jdg(mmQtQ8UCi$-fi@8v8*AMWt>S#*?%_cr9q0|^GAJ!8 zv+qwA@!K`O2k=!agj*zcl)o2?V8)9r%Je!<0IWS7hVeU+nB;6J{GIfG;JWvWdR)F9 zv+Q++i%DmKRNaVs`_d!37Dwe%+>ag$2dzwS!J#fL6oG^P8nYNWq{(fjj?-+p_(ph>kzzL@<82x#~-eCbkKc}cN-W<;0-rw=6_QxoZq{(5G5e|{4XLBy?0vjS(=A= zfGUbN2)a2b`+{wJHh$!36<$Zw3WH<06zDKa1E&m@qa`zNi>A$(2dl2uu2V6MnSoM>OWy?_56 z7;KQi7XuvTg`&PYa~UY@17rJOkmLt^x__2wziF)nFl!y=2mkWR+Z3`VqZZcBgqv3F z=CG+z0(5Rwn{8ZHFz$3yR{8uPqio3VPY)A*uM`*<52`5R2=WV|T!P zwghu-qn@;N2+$6Ti@%kM4LtOkeH0ZH?_8W3T&uaxH$Z@*_Gwi3`E%|4U}KbrUhD01 zp~)$(>|gC_slmZ}2CyYsqpWVyE;ZKwV|L|pyC<#j{Q0E3WIFhbkR(7S7P3PN89zx~9W2Ag= z(MUN>Sn1_O&wZjh)*Tu=#?{F@OLKYc`MpM?p0KB6M&%|(#pY}#eMps~jJY|1>63-U z-?1RX`hc1C_)M-J%Kp|Z-Dhew&SRSFmFvEohLzvop2o(;4XOq!pKOnqrj)JM_>D_b zdKF91#T-y>guy)0z8rp?R<*u0>c+ZSsYEB{64`*Y-j5ZkaTTEWo`ub8mGcjNR)k#j_>V^I_CRNBcKlI&W#klg28nb7 zKv>8Sxosxsf#2?w(6eXCWdYZ}+NsK>sGga#xV>lb6t;QAX8FXeZRXW^W>3OQbwG)! z<=kZ$TaUiB7;8*wCbS1To3Rz5VYRwd<2jQT#@1^n<}_k+F!cxt^uN_C%EI}A$DJz1*OHgQ=p!L(HefLa#h zV-ub83Mrv_T#q3@uH%y(M$Bqm%QZWLe!$8}X+FH2*}mWTEkMgZYqYG)k zPQ#6w}BqHnvlM0>29sWL&euZj1v~s{(Kf#h{TPPXjW)G{t_dW`=$h;9)t=+>-XmP>j5&*d%Drdsgo4QTKYoqj zn)Ot*D=cL&>*8Yn$4AoLX_NLE&%wE?J9lz%yCET`w)bc0##0ptk9T-4JaK&{S7mDC z)Z&3Nc|_K>6kZ{j7CHVpoXL#tCAAEt!+Dd9km(NrUSh6uhZjso%kvDIus(3T|L`XP zh_9=Ur?+vXcY`ZMWvz}ihdQ-b;Cih_rE;zvue7ulFGw(SGeW*5&G>bKuH8vv_4^z}OeBUGq@htnzE!m4)@5G`WJZ z(o#Z_4UEUza;aNXe5N^#RJ#Yuosd%|<64_moJj8t*eF}SxbxzG-abeHKxSNW$z#PG zVx%+}VvWcU((1hlg%nVuW13WB@`f1O<~%x%l(59GOnlqJd#sY}@R(7?=8Z3hk`Ihb zre;*6w^uKmL7n3yPs2&F1e`Rh zs9e&8JyQ+4197C($0s{xHKu)jpnC-G_!lW`;^wcvqDvi?r)nH+Dr=y!TQRibMvb+H z5qgM*kwT4uy+r;vuU${u-Xbh!6rLt&H1L=+QO(WxT6hk75M#fpYq_|D3MZx2&U1eS zY3#d$!*+jV{{02&G}-{n-^(4*@tTfQkVdBIx<>!aLr7Hb*A|0vCs_?z4u&Xt@k*^L zhhG4!#%j^CYj>WwL{V|_%YvZG(rv1xm=-Ysaywhf0Xty z=tql-DJdy1y?oE^)H>(1VO{^rHkHZMp{EhuQ-^CFu9HnL*Sr6~XH5`R2371aTt=n; zT*djin0B|0&Z}P3mJzS`B`5%_1DAb3XEft(Jg-@EP{8WSE@e4tu*$C63S9QZ4ClQm zXy3z%QCWy7_SpHJ_h_>_CiA&N*S=1TqoGmxjPXx*mBw+|ZuC*02~IcF-0vxJyE^Z*~Hd<^-WLG&Piq? zj=00?+X1CXm2WVmkr7ed!Du8ltiZEOUnc~6cvl{)@_98z5vbd*${*tc8%0TBoTJ0aHJ4GpSttjxisUFU8@0AR{=>~}u z8$m_9cPCzT*smPFH|5pYLjW*SCVL?yE@Q4PL(C-y@>R@@5nEZ3A?7wYWn8G4X^N`6 z{E>My-2`epy(<;!`sU3)pm?YSI}4_5dauPz^H_f zj}-|C@XFJ&=|dxMAbd;i+&WvpTCv;-9rc(mk2U-_V~mJ`f})C5$$vW7I}QNv7%028%yi2z z<7s0Oz4WYHgtN(5q)kc+U~Ezm)nigs>g+t-W9*ppTDKp(TJDIUWB&2OGjWU!EW>td zX|gqPS;@$=gxT!Ur$ZP7OaVymr(DCB!_zq0EW~DTi9zpLPv_K6kE2RT?yFr{?Hsqo zGHrONDPN~*U7_b)o0gu=PNmylU0eNQg_@}Z)hj%X#?w&hF(}hgeqn>|L_F>f0Nh1K z%gvOKxpLuVT3HexM3d9UK%c1^mwd=(@IEYm7|GG~9QLnr(W{a~nu$o^1D{7w^#e~L z&n~Xsg7eqVkWO7lzDax9lT;;+e0~q4=P0`9ButbG1BnVtH~(a^=^vLu)kLIIWkBef#l4xof=B-aEq%QZO zzn2Tg3;3mcV-*z?^mH43UG9*uCxS(mwE<~XGZDJ*CXuzEJ&H2Y2Yx-mzDBIUi^j7? z0_f~MDdVtoHg8{lXtt1=;uHLt*DD3|7CH5dIX5Sfzaz{lSj)e>)ueI);HMq-ny(Ts zpYWMh25qu*KlecDe)=Z)xj_?#FHc1|eB;L#HB=z)u%3XP5WE{)r#SBLTysabA0+Pn zP?wajOCo|uHUbL5@!%isp(n4#%FWEK@Agz*+iTNO({==M|3o4)FFla%o7q@!GFH+? zkDOhJ6kw>NLM~SpKSnT_n_mwr$p@V^+bAjB2M7V6A*NR4ZN<61Q^-yx+d!|!&D-^+ zX^K-2$|wluH3rk)0GYE(0rL1cc{`Y_z!zL*o8?Y;fSa3|s%$9|kE zyFB$uykWW!{~f5?Fr6}0{42T9o=UJ_+m8}X+2Oh*&rRp{CaQs7!><*6Mjae7cGKQa z&vr-opQ^{(M@4Hx`3#1i1@d6Bva?-DhWAE!eE<|>qEeuqb}V-AsFMaSBB-uYuIU0A zoc{G|37a8?XxK6u*=Ra_9o0}9lTo|UzkE6O7vS~lI}7NE+nF9X-yy46`KVR)Nz6Dh zLKugfq6Ea&*^Hfv0EHeeHvqSCx3J(jk&N&L_P6;R; zMC6YiJvuD(-ab=N+=IsfwX`|pD*X5-z>i?u2BaK@*EHyWhJ8a$t&r^lAgvj{U^&7b zLq0KOr4Y5!la`Br$2>Pz=Q?w|EBWSUl$y+ynxK`flXVq9NvZGvs?Fy@)1(fPz4_4R z{u_TyN^bt`Jk?|%`;l`9`3aymR2WPK)2ej*9ModDESkRNCRqc6%D^}2^<@lX<7b4k zTBK1@$8IRlya0eH{;SsruDN~1<=H3q`ZYWdl(hMqQAxJGfoyl754`>1mWC1xks_7+fv}OZ9Ywnn; z&gM0t;v7aVq|Kuv^h*=L>5lBPOgm5{>wU7g+(VgvIQDGsF26apFW~fO`eOyx&ib?k zTH^{(^`{p<*`5>%Fag93=_n-9^t!ds@E2W^-A*9U++h}{n+zEfZd%EW1#YBj-jmFi zCR#T3^O5z6LlCwYJ(_L*Eb=kBk&l32O6^$0sPS}rEckogCCO2`_~A~B|n^mINGVXbR_F~{N80?&(#f0zRu@;=!HcVQnfC3j}uaHY~IpWxp= z;5}uY`2f)g5Zp7re$5Tj%KPoPgK8#jS5dKJ zGLybDmKF|vMd+1hE#SWYJZ&IT67A+?*Yl|&VF*mT9>XYB0dU*%%_IPGHBzQlcs6}Q z8y0G1lQN1fwI4kJ$fKS4y1MmlJ=G5?xUJbLv)=KYmA5OsN%k`n2^cYF&8`m>yWdxS zi=+C@8k{Rss)wG2ck}C$I@fTyGAF%WSvfe1OiE%?GyM7TT5*?TwBtVVLAs1r?eTt| z-V2|@yPVte<>;2y-%+Vtse%fs0al}>IvTS%#}&AfU4f9{uSgt_9E}pdd7xM2Ws?ZDmt?Oebr%0*S$YUXI2;?nVIG%ckp_zZ2y& z+a_MPS*-7ZBr~Nbp}(zze;}0(tL_p65D!DU&N@D27vRokQz_{4RiW`l{}m_Oe)v{k z4pxq@RZS{z%`_#!CQ!d3otaCw*Dgs7y;h#GymUg z7eFV;{Flf6;{Tl2zU2wUzF^lN-a+0b51{Ot@BEME17H_?`y5OyPo6$yybG8mnwc)g zZYaY4CbRedOt;D1tDq6F3u^wLa76WNDc~^qaDM;L_B&E;zzOa43}5#j`b!hwxc!C_ z^B?TbQ&CeJSIbXM*X=z6)Zr4Wz@+v3^~YhN6R7~x^FV#~GAb&@td<_2W1BWO_5)^< zT36FlemBO|4C_>Jz1b-d$_0$2tE)wwrUrM3@C}vc7_5F`T;b@`5lv$VH!TZd`!7BT zjq(_o?(QBE_k*j~)&ht_r&%1}!a{4buf?&1wcTQ8|GAX0wT8UjP?Gz zkZIrTAnXrY5zkn{r>g0*CBaBczbDbtH&l(t8W-Js0X z{fD&0X6`-Jo4jUqMmgo2Jt?#2faY;Sn(5YH8X%B)T=3jw4qJ~z~C!i3f97{&j}?bL{uOD=yiw2Q=T zos9NkOp)OlD{qs_&9S4KLI~{M6cUY8iL&iA>S;~5!*_pubEMEyfAu9Lm!abnzmT^G zhejHE@0$An|MYC7Pot~HZ26rtuApB|q_+rKQWH0}D*|_L=HgyJap4U{cmw3uP_~HU z_FW3=WlFUtV$Ry=PIR5qsRhXvdJ>>pFifV$Ov0?p++<{AsnGw>3W5uNicHkB2KCfZ z`@o2eLBN*5XHltM#-52yD%31DuQe{d*Bm%j&XxMG;Z7CEZ&>>z%wvkgAWWV{)+JvJrt;R3ngr(1J1+O4;BZ{~0X zMaAn0n3=!v8A62HkRMF`_kNQ`V=RCs99LbY0S>1&cz2s7SBZ2CtWug0UoHAS@D(cN zh<#DF>(><46q9YgWJ6Oy7dvsX`OkTzn&b*y$H!^>_9l3E(`1f}t~8V-B>X3yZY}Uw zECg&Nn_;H}scV9JaS6X8`YwHTfgxthagu?-UL)IgsjIct#T3i%+&I+{$Yp{UHVH_n zX;rP}aoO#|y`C{AtbKk>Wn!#bb5nqzUDr>m)pTG$_rnZxCY8BR*7X2#Ifwhzza@LN zC?u$tO2anVwr^vRbh2`_H?_`@63uO)lY9S03fKM(5J>b4m6KCb$D3nUe=Otd2eV*N z_=#qXZt`|#=TZoE;{?~AVa#9ifRr>vK0(5V%cRM5#}pXq52}?qECKj(;V@he2%wcp zdZFp=o5QxVf4Ai-#jOuyndG@MJFMInt9Y}R=vx;0OsC0ss@f1B+(?Ls#8*7z%d*)u zw%%V|bXlzXbsM2vGjC+~9}%68Xgq1e0`ov?eV+OkC%ZpTL?%IPe_AU+;^qc3=DfT- zfxdfON{xy&#l!lu%2Ai;o&Y1nqHxF_GdtfL3_*9^IF$&RKLNZsMHWYJP>|EuXWZvJ zKz{?(`>WtTP zdUkhwC!`NtRDW6hcFZtxfFRVZYv@(xa}f~{wM5o^1D?Y32vS=2ohj__D)->kmDW{n zht+B@rgkL)AQFnn*hFpmz77G_BdjGL@ALHaQ`8fgIF5gtAGLeJWvY4@iNZhiNy+{A z5s(QaZUjl6N8Ln~4v{2|0lpj@R~lWd3Csz8ojojSRFGL7al`VQI4FG)AQKps8d1)$RmbCO<~V}i=XobBvRw3eeatj6O^t8#eK@k$792cIy(6>|DZ3CDvx6)0JE zVtWX{{6S84vZYVs!lF_DGpI5l{ZcVKEjcdaPO>sZ5{ixrow-rr`r4Gw`ibC!ypj~D z^ZAC?ALRR>%E5BKLx25<+(gBOn^^9?shxdlz}S|^0Z+!$ zh!G?tuAMUrxDMdKR+*l7%8`{XQ>lv5z;=oOpi|k^=h`DM4@dzM(03MEI->_LfTVgXCtqomA zf3}_zKY7Aq%981`d5izrDb3XoFg5^EF5o-2Um~NI^4|X$kw{GrqHPqT7IIwz8i*~M z{a_}3!D!jU%#4nj+Ig;W$K>TpfLn7Khob!rNf4mMET49q zo#8ByTO!zX8da9bi3#(1Z%!7L*SGs74w3*z#bY3^7Kl~kPvS$ofbl6Biz^2_DJ4eb z178d(qUmh9t?<30*?=%&o}(VttrzNWB}Y74ioO55HMOF$Art%0j{< z(S@|U0tS$ja`PImx-7D*tL1Wj>9b}eBsyC-Up>Gm^6!vSrfSTehBrfrWmDco7GqFGD6j zb$shLh0yB|eCtQ9F-O1Mxe}*!CR?TCHfpKwvmNjn^0deO0F4R0GIf#0Jnp``|ls5kDsix&Is+hZV~U7vI?iP4DJI{ z0U)0W{r+SGU9jBey+;|F2tpN$%Beup%b1_T$-5<5A2GIq>N|xc%nN|1Z8w z`%s^bZ$-_6eyJTr|>%lzNJ(`9l;f5Q!f7s*HFPi;-u59D-?Zy7J|K^ZvX~;t^G|YV5N^o%# z5;D^~c`zz0Dr~VWvwHclgx3%o8?6tld&qMZ29$k*g^c}xDkbEM>rRw#A`vQ;OwyH_ zis})S)~a`kgCMKgw3R_Noz`yG@2XRNjNMqbL4(F(_@wUiy@+NDFOQ47kEnbBt<^~% zbSCrZ`04AI3!ko@n5rbg~aDj)G{s zG>e9{*gwe4SDQ(mnd~2(VPpfbD=OMAXJ_C7_R>V8Uw?*b!Wu{eL@yxM1W$R+S;OSq&FxQmM zOuf#a0m7z`fUUW9wQF={X^srP)9=;UvC32-;m=cHf+u0njSH18wgYad!**x8U`9fj z=KViT2WdVP;WU?GNKzubn;pd9gwf_CoM>_DL+{l}DyOE>#^WnYLhk?v|}fFm{wn<+*b zuT@A(6%x|G6N2zaIEQ&VDVe(n4UbKrlE|cIwB;7V6X4xLVbIU(5?s zlQ`uWlK^*o(`{bH{n%tArl?}4x%z8*sRkSZ1fgIVZh_p3*3kEvc!eYW1VhM#^pcPg zv@#e;bL{^fkA}E1!V+?SHC!0wX-?OC%=^>QvS7K8OBm`iRQ4S3%bz>np8upd=N-~1 zKF{&qO|U?iKZ)Bqu6SVB+OWCk=jX1pX?_Fu+5!7i#q(!MaOtLvqf+Zt^Dp5%{pyJv zA{q{^#Wx!_I|eqM9yRTfSh#0UtU-IEZ)V!BmeTh60g`ck3u)_|cbEXeFUzc(u9Q1{ zr+!?={m)an2)AtgUKc%^KVy^b0jGJzcSz^OY0gBIx0JiF9CagR1yBu{he4%5A$L-< zCfpA-b@i+>MyXD-@0CexNq}@Z20h7sB0TV>Wxnoi5@+9WT&N%5q(%__hkrnlIZFaTABeEtSwv9Qo z<_o>2gXcO~5i^j3KVucurG|tO5>K4628LDZx>~2R!o$NsQFRmUwso|gv9r=%%iTL* zr#RX=JeuRxtrM8Dt*~M^IC;=|k4^U6FBpuLO4CD;kenj$Quzv_4i^h`mK37jW`l69 zf2N;zKI9BE99&LE8}`s$Rj#-7E79Q)BxV7Jhe!e%E*kCWd_!hQ9~Un_a9S0nC*#jP zQ;!6jh{zY8s809=9&vOCh1?qaD7QVrj?oix!(QSM&zvwc+3$NbyV_c8vks4}SHJzn zJc%xmCd5|5LvBxwobQz!s(ws&P_|H#q#~oBE*H|eN_l2n@ zq{Zo-4=%bHV!W&S*7@d&;e;b~pi765a}yU;7b&0hKV$d1lhTy~9z|3S6T0D3O@`U% zris;JO z&QD`@FW6jXj}|PeE-05n6-q~?E8|ol?$hDygkATqlL4y0$+V14Pip40_ipRhM_Krb zVF#ZFRW_kn=J{rdCpusjH#I%>yB|ABml!$cN&<-d6;lj@d z=8$QV?4SFeS=kj49^tQgrPudRDV#C3%jA>0(!`FE>kFKFs7}k*zgf1lX0a?U+9aUo znVs)vpD+ge{Ws5QM7a!g)k$Aci=Gvar&{yi16wsS@$wz){lUh| z&Bdb?Xh@iS`P|nGMTM|mlf$B%o7ydNTv;c|&nE)M3qQ+AaEb|t22qeR(v!2ldTp1M zM%}%1Xl->|{^%=I2^j%@K|{+p)KvX3E%Kn=$DU1abM3_3TR)xS$Hqagm^C`n10N9g zxIkD8dkC33*CN9nim3@vbUTyMB=$a2TEP5qFykvdYAX9zUY>HI=H+l#AW>5AkatHh z@wDbymLB=$xKMUSFQknpl;Wfo)A7iISHv4Fd_KgVV41|2p~Rg|J@j3>ah`NwBE$iE zU>5AO=Ea{i_NV1#W!n@*U1B<|mV=J^ZIurFwc%H(yES(SePFju$_%lkJRbR73)nb| z=H`tH^(`LRr80T2-?7uQF8@b4)rZs6e;kdOE@wZq{%}j@*?aW((DYnJ z-@SHDs2VrQ`$RMoF>QZPfqZhcR?8o}yhyy+)=QueHe}{45}Zv42-2=}n@eh7Sv7=B zqV&oP>hWAK`7=CXEei3NtDyn5LSbUo$E#}h6?t@6ru05hdNk3x z11Yd+*5oNR$}2g8LZIG{GS$Su(?25&!tIIRLq0@sIpJ0Wb%(k|jE>SSYBRCo?&r;c zUcrbJl}=H<(vlR>hBQ}+_Qp6*LE#{LxaXbKJ#y>c#)9vT(GVUfD@SKxxQAtDG$-&b zwb$^)@4C^5PYP{gS&D3x;`XZ2_NS};g~9BuFZ@mqN{l-tykGx*IyOj>Fry0(UCzmgnmnk89I0KIg z4M)ds)1oyRj!~Wb^LHaYHB1yHz)wbu+~@^a2VFCDBd`BV*Zo*LDCY$N=iy)UO`a%2 z=0>2ruJ5*aqB`$o+YcY5R)iJg)7va5xGR^I2*#4$8JzF$cgw(J|F)9DhIVNEoChR8aP{ecA(Z$HLHAlgc zrYdtjCmP=ukc;kWrZ1_3E)0WHHh)=-z#6$@1Hs3^Co8KN@4g#L9Gi@=C4~;o1xVN) z0k)GtH>sdUnBvO3xMESSi~Ns9j4M@YLZu<^>!Dt7E z%WJzf&+NSTzIvUPR>$kj8P*|ZBGuBb^om`V-_x<*HnlkQOZ~^H&og&#m*2Z)n*C@0 zvy1e0Y%(#HyuPF3&GonLjSC+Ndj6|r_t+f7cR70c1k;KN+dGy2=FMC=@z=?noGJ$& zPnYjHEpc&{@b+`}YqxDmSG_3<&dH3;Cs^v=ZvVT)=+yQX=Nis`eG6Ju%22Z78K`l9 z-nVK!awmJ;?{}T~tq1S_2QS~fsj5`H Date: Wed, 11 Feb 2026 15:47:01 +0100 Subject: [PATCH 259/293] Add workflow to test seeding in all locales There is a `script/i18n/test_seed_all_locales` to test seeding in all locales, or one locale. A GitHub action tests all locales seeding every week on Sunday at 2 AM UTC. It can also be triggered manually. Example: script/i18n/test_seed_all_locales # Seed all locales sequentially script/i18n/test_seed_all_locales --list # Output available locales as JSON script/i18n/test_seed_all_locales zh-CN # Seed a single locale --- .github/workflows/seed-all-locales.yml | 102 ++++++++++++++ script/i18n/test_seed_all_locales | 185 +++++++++++++++++++++++++ 2 files changed, 287 insertions(+) create mode 100644 .github/workflows/seed-all-locales.yml create mode 100755 script/i18n/test_seed_all_locales diff --git a/.github/workflows/seed-all-locales.yml b/.github/workflows/seed-all-locales.yml new file mode 100644 index 00000000000..93ce07c08c4 --- /dev/null +++ b/.github/workflows/seed-all-locales.yml @@ -0,0 +1,102 @@ +name: Test seeding in all locales + +on: + schedule: + - cron: '0 2 * * 0' # Weekly on Sunday at 2 AM UTC + workflow_dispatch: + inputs: + ref: + description: 'Git ref to test (branch, tag, or SHA). Defaults to latest release branch.' + required: false + type: string + +permissions: + contents: read + +jobs: + prepare: + if: github.repository == 'opf/openproject' + name: Prepare + runs-on: ubuntu-latest + outputs: + locales: ${{ steps.list.outputs.locales }} + ref: ${{ steps.use_input_or_find_latest_release.outputs.ref }} + steps: + - name: Determine git ref to test seeding in + id: use_input_or_find_latest_release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + INPUT_REF: ${{ inputs.ref }} + run: | + if [ -n "$INPUT_REF" ]; then + echo "ref=$INPUT_REF" >> "$GITHUB_OUTPUT" + else + BRANCH=$(gh api repos/opf/openproject/branches --paginate --jq '.[].name' | grep '^release/' | sort --version-sort | tail -1) + if [ -z "$BRANCH" ]; then + echo "Error: no release branch found" + exit 1 + fi + echo "Found latest release branch: $BRANCH" + echo "ref=$BRANCH" >> "$GITHUB_OUTPUT" + fi + + - name: Checkout + uses: actions/checkout@v6 + with: + ref: ${{ steps.use_input_or_find_latest_release.outputs.ref }} + + - name: List available locales + id: list + run: | + locales=$(ruby script/i18n/test_seed_all_locales --list) + echo "locales=$locales" >> "$GITHUB_OUTPUT" + + seed: + needs: prepare + if: github.repository == 'opf/openproject' + name: Seed ${{ matrix.locale }} + runs-on: ubuntu-latest + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + locale: ${{ fromJson(needs.prepare.outputs.locales) }} + services: + postgres: + image: postgres:17 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + env: + DATABASE_URL: postgres://postgres:postgres@localhost:5432/openproject_seed_test + RAILS_ENV: development + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + ref: ${{ needs.prepare.outputs.ref }} + + - name: Install system dependencies + run: sudo apt-get update && sudo apt-get install -y libpq-dev + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + + - name: Configure database + run: | + cat > config/database.yml <<'EOF' + development: + url: <%= ENV["DATABASE_URL"] %> + EOF + + - name: Seed locale ${{ matrix.locale }} + run: ruby script/i18n/test_seed_all_locales ${{ matrix.locale }} diff --git a/script/i18n/test_seed_all_locales b/script/i18n/test_seed_all_locales new file mode 100755 index 00000000000..36d01e83104 --- /dev/null +++ b/script/i18n/test_seed_all_locales @@ -0,0 +1,185 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# Tests that seeding works in all available locales. +# +# Usage: +# script/i18n/test_seed_all_locales # Seed all locales sequentially +# script/i18n/test_seed_all_locales --list # Output available locales as JSON +# script/i18n/test_seed_all_locales zh-CN # Seed a single locale + +require "pathname" +require "json" + +class SeedAllLocales + class << self + def call(args) + case args.first + when "-h", "--help" + print_usage + when "--list" + puts JSON.generate(available_locales) + when nil + seed_all_locales + else + locale = args.first + validate_locale!(locale) + seed_one_locale(locale) + end + end + + private + + def print_usage + puts <<~USAGE + Usage: #{$0} [OPTIONS] [LOCALE] + + Tests that seeding works in all available locales. + Uses the current development database. + + Options: + -h, --help Show this help message + --list Output available locales as JSON array + + Arguments: + LOCALE Seed a single locale (e.g. zh-CN, de, en) + + Examples: + #{$0} # Seed all locales sequentially + #{$0} --list # Output available locales as JSON + #{$0} zh-CN # Seed a single locale + USAGE + end + + def validate_locale!(locale) + return if available_locales.include?(locale) + + warn "Error: unknown locale '#{locale}'" + warn "Available locales: #{available_locales.join(', ')}" + exit 1 + end + + def available_locales + @available_locales ||= + rails_root.glob("config/locales/**/*.yml") + .map { |f| f.basename.to_s.split(".", 2).first } + .reject { |l| l.start_with?("js-") || l == "lol" } + .uniq + .sort + end + + def rails_root + @rails_root ||= + Pathname.new(__dir__) + .ascend + .find { |dir| dir.join("Gemfile").exist? } + .tap { |dir| raise "Unable to find Rails root directory (looking up from #{__dir__})" if dir.nil? } + end + + # Runs all locales sequentially and reports all failures at the end. + def seed_all_locales + locales = available_locales + puts "Testing seeding in #{locales.count} locales" + puts "Locales: #{locales.join(', ')}" + puts + + unless setup_schema + puts "ERROR: Database schema setup failed. Cannot continue." + exit 1 + end + + results = {} + locales.each_with_index do |locale, index| + puts + puts "=== [#{index + 1}/#{locales.count}] Seeding locale: #{locale} ===" + success = reset_and_seed(locale) + results[locale] = success + status = success ? "OK" : "FAILED" + puts "--- #{locale}: #{status} ---" + end + + print_summary(results) + end + + # Runs a single locale and exits with appropriate status code. + def seed_one_locale(locale) + puts "=== Seeding locale: #{locale} ===" + + terminate_db_connections + unless run("bin/rails", "db:drop", "db:create", "db:migrate") + puts "--- #{locale}: FAILED (database setup) ---" + exit 1 + end + + unless run("bin/rails", "db:seed", env: { "OPENPROJECT_SEED_LOCALE" => locale }) + puts "--- #{locale}: FAILED (seeding) ---" + exit 1 + end + + puts "--- #{locale}: OK ---" + end + + def setup_schema + puts "=== Setting up database schema ===" + terminate_db_connections + run("bin/rails", "db:drop", "db:create", "db:migrate", "db:schema:dump") + end + + def reset_and_seed(locale) + terminate_db_connections + run("bin/rails", "db:drop", "db:create", "db:schema:load") && + run("bin/rails", "db:seed", env: { "OPENPROJECT_SEED_LOCALE" => locale }) + end + + def terminate_db_connections + db_name = database_name + return unless db_name + + run("psql", "-d", "postgres", "-c", + "SELECT pg_terminate_backend(pid) FROM pg_stat_activity " \ + "WHERE datname = '#{db_name}' AND pid <> pg_backend_pid()") + end + + def database_name + @database_name ||= begin + require "uri" + db_url = ENV.fetch("DATABASE_URL", nil) + if db_url + URI.parse(db_url).path.delete_prefix("/") + else + # Fall back to asking Rails; use .split.last to ignore any + # extra output from initializers (e.g. REPL commands messages) + `bin/rails runner "print ActiveRecord::Base.connection_db_config.database"`.split.last + end + end + end + + def run(*cmd, env: {}) + env_str = env.map { |k, v| "#{k}=#{v}" }.join(" ") + puts " $ #{env_str} #{cmd.join(' ')}".strip + system(env, *cmd, chdir: rails_root.to_s) + end + + def print_summary(results) + failed = results.reject { |_, success| success } + succeeded = results.select { |_, success| success } + + puts <<~SUMMARY + ================================ + SUMMARY: #{succeeded.count} succeeded, #{failed.count} failed out of #{results.count} locales + ================================ + SUMMARY + + if failed.any? + puts <<~FAILED + + Failed locales: + #{failed.keys.map { |locale| " - #{locale}" }.join("\n")} + FAILED + exit 1 + end + end + end +end + +SeedAllLocales.call(ARGV) From 912c4cfd411d197668f0624a71e89b80086df535 Mon Sep 17 00:00:00 2001 From: Tobias Dillmann Date: Mon, 2 Feb 2026 11:18:05 +0100 Subject: [PATCH 260/293] [#71248] Add new Sprint model --- modules/backlogs/app/models/agile/sprint.rb | 61 +++++++++++++++++++ .../migrate/20260202093215_create_sprints.rb | 42 +++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 modules/backlogs/app/models/agile/sprint.rb create mode 100644 modules/backlogs/db/migrate/20260202093215_create_sprints.rb diff --git a/modules/backlogs/app/models/agile/sprint.rb b/modules/backlogs/app/models/agile/sprint.rb new file mode 100644 index 00000000000..102b60c3204 --- /dev/null +++ b/modules/backlogs/app/models/agile/sprint.rb @@ -0,0 +1,61 @@ +# 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. +#++ + +# Intended to eventually replace the `Sprint` model from models/sprint.rb +# Namespaced for now so that the rest of the application can keep using the old model. +# Remove this namespace and the old class once all usages have been replaced. +module Agile + class Sprint < ApplicationRecord + self.table_name = "sprints" + + enum :status, { + "in planning" => "in_planning", + "active" => "active", + "completed" => "completed" + }, default: "in_planning" + + validates :name, presence: true + validates :status, presence: true, inclusion: { in: statuses.keys } + validates :start_date, presence: true + validates :end_date, presence: true + + validate :end_date_after_start_date + + private + + def end_date_after_start_date + return if end_date.blank? || start_date.blank? + + if end_date < start_date + errors.add(:end_date, "must be after the start date") + end + end + end +end diff --git a/modules/backlogs/db/migrate/20260202093215_create_sprints.rb b/modules/backlogs/db/migrate/20260202093215_create_sprints.rb new file mode 100644 index 00000000000..b12c212d09f --- /dev/null +++ b/modules/backlogs/db/migrate/20260202093215_create_sprints.rb @@ -0,0 +1,42 @@ +# 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 CreateSprints < ActiveRecord::Migration[8.0] + def change + create_table :sprints do |t| + t.string :name, null: false + t.string :status, null: false, default: "in_planning" + t.date :start_date, null: false + t.date :end_date, null: false + + t.timestamps + end + end +end From d4ec81c3d5bb119095a5f66796a656740f72a5c1 Mon Sep 17 00:00:00 2001 From: Tobias Dillmann Date: Mon, 2 Feb 2026 16:19:26 +0100 Subject: [PATCH 261/293] [#71248] Connect Sprints to WorkPackages --- modules/backlogs/app/models/agile/sprint.rb | 5 +++ ...02150252_add_sprint_id_to_work_packages.rb | 37 +++++++++++++++++++ .../backlogs/patches/work_package_patch.rb | 2 + 3 files changed, 44 insertions(+) create mode 100644 modules/backlogs/db/migrate/20260202150252_add_sprint_id_to_work_packages.rb diff --git a/modules/backlogs/app/models/agile/sprint.rb b/modules/backlogs/app/models/agile/sprint.rb index 102b60c3204..1c3e963ece1 100644 --- a/modules/backlogs/app/models/agile/sprint.rb +++ b/modules/backlogs/app/models/agile/sprint.rb @@ -35,6 +35,9 @@ module Agile class Sprint < ApplicationRecord self.table_name = "sprints" + has_many :work_packages, dependent: :nullify + has_many :projects, through: :work_packages + enum :status, { "in planning" => "in_planning", "active" => "active", @@ -48,6 +51,8 @@ module Agile validate :end_date_after_start_date + # TODO: sharing, work package journal + private def end_date_after_start_date diff --git a/modules/backlogs/db/migrate/20260202150252_add_sprint_id_to_work_packages.rb b/modules/backlogs/db/migrate/20260202150252_add_sprint_id_to_work_packages.rb new file mode 100644 index 00000000000..ff3094cabaa --- /dev/null +++ b/modules/backlogs/db/migrate/20260202150252_add_sprint_id_to_work_packages.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 AddSprintIdToWorkPackages < ActiveRecord::Migration[8.0] + def change + add_column :work_packages, :sprint_id, :integer, default: nil, null: true + + add_index :work_packages, :sprint_id + end +end diff --git a/modules/backlogs/lib/open_project/backlogs/patches/work_package_patch.rb b/modules/backlogs/lib/open_project/backlogs/patches/work_package_patch.rb index b7b13d19817..ff141761a77 100644 --- a/modules/backlogs/lib/open_project/backlogs/patches/work_package_patch.rb +++ b/modules/backlogs/lib/open_project/backlogs/patches/work_package_patch.rb @@ -41,6 +41,8 @@ module OpenProject::Backlogs::Patches::WorkPackagePatch less_than: 10_000, if: -> { backlogs_enabled? } + belongs_to :sprint, class_name: "Agile::Sprint" + include OpenProject::Backlogs::List end From a766694c9d993ad5d166bf503a2abb93e6751ec5 Mon Sep 17 00:00:00 2001 From: Tobias Dillmann Date: Tue, 3 Feb 2026 09:51:55 +0100 Subject: [PATCH 262/293] [#71248] Journal Sprint property for Work Packages --- app/contracts/work_packages/base_contract.rb | 3 ++ app/models/work_package/journalized.rb | 1 + config/locales/en.yml | 1 + ..._add_sprint_id_to_work_package_journals.rb | 35 +++++++++++++++++++ .../backlogs/patches/work_package_patch.rb | 2 +- 5 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 modules/backlogs/db/migrate/20260202150253_add_sprint_id_to_work_package_journals.rb diff --git a/app/contracts/work_packages/base_contract.rb b/app/contracts/work_packages/base_contract.rb index e7107492767..94e0dd6bf3b 100644 --- a/app/contracts/work_packages/base_contract.rb +++ b/app/contracts/work_packages/base_contract.rb @@ -136,6 +136,9 @@ module WorkPackages unless: -> { model.type&.replacement_pattern_defined_for?(:subject) } validates :subject, length: { maximum: 255 } + # TODO: add validation, check permission + attribute :sprint_id + validates :due_date, date: { after_or_equal_to: :start_date, message: :greater_than_or_equal_to_start_date, diff --git a/app/models/work_package/journalized.rb b/app/models/work_package/journalized.rb index 9f81b0f74bc..01347465f5e 100644 --- a/app/models/work_package/journalized.rb +++ b/app/models/work_package/journalized.rb @@ -110,6 +110,7 @@ module WorkPackage::Journalized :assigned_to_id, :priority_id, :category_id, :version_id, :author_id, :responsible_id, + :sprint_id, formatter_key: :named_association register_journal_formatted_fields :start_date, :due_date, formatter_key: :datetime register_journal_formatted_fields :subject, formatter_key: :plaintext diff --git a/config/locales/en.yml b/config/locales/en.yml index e823a724929..c309e81f84c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2200,6 +2200,7 @@ en: role: "Role" roles: "Roles" search: "Search" + sprint: "Sprint" start_date: "Start date" status: "Status" state: "State" diff --git a/modules/backlogs/db/migrate/20260202150253_add_sprint_id_to_work_package_journals.rb b/modules/backlogs/db/migrate/20260202150253_add_sprint_id_to_work_package_journals.rb new file mode 100644 index 00000000000..ec0dde79862 --- /dev/null +++ b/modules/backlogs/db/migrate/20260202150253_add_sprint_id_to_work_package_journals.rb @@ -0,0 +1,35 @@ +# 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 AddSprintIdToWorkPackageJournals < ActiveRecord::Migration[8.0] + def change + add_column :work_package_journals, :sprint_id, :integer, default: nil, null: true + end +end diff --git a/modules/backlogs/lib/open_project/backlogs/patches/work_package_patch.rb b/modules/backlogs/lib/open_project/backlogs/patches/work_package_patch.rb index ff141761a77..7d8bade7c75 100644 --- a/modules/backlogs/lib/open_project/backlogs/patches/work_package_patch.rb +++ b/modules/backlogs/lib/open_project/backlogs/patches/work_package_patch.rb @@ -41,7 +41,7 @@ module OpenProject::Backlogs::Patches::WorkPackagePatch less_than: 10_000, if: -> { backlogs_enabled? } - belongs_to :sprint, class_name: "Agile::Sprint" + belongs_to :sprint, class_name: "Agile::Sprint", optional: true include OpenProject::Backlogs::List end From dd8f5e8d6a7d5d428ae586c0acac9fb1a9752e33 Mon Sep 17 00:00:00 2001 From: Tobias Dillmann Date: Tue, 3 Feb 2026 12:23:37 +0100 Subject: [PATCH 263/293] [#71248] Basic Sprint specs --- config/locales/en.yml | 1 + modules/backlogs/app/models/agile/sprint.rb | 8 +- .../backlogs/spec/factories/sprint_factory.rb | 7 ++ .../backlogs/spec/models/agile/sprint_spec.rb | 84 +++++++++++++++ .../work_package/sprint_journaling_spec.rb | 101 ++++++++++++++++++ 5 files changed, 197 insertions(+), 4 deletions(-) create mode 100644 modules/backlogs/spec/models/agile/sprint_spec.rb create mode 100644 spec/models/work_package/sprint_journaling_spec.rb diff --git a/config/locales/en.yml b/config/locales/en.yml index c309e81f84c..68c794cc535 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2161,6 +2161,7 @@ en: estimated_hours: "Work" estimated_time: "Work" email: "Email" + end_date: "End date" entity_type: "Entity" expires_at: "Expires on" firstname: "First name" diff --git a/modules/backlogs/app/models/agile/sprint.rb b/modules/backlogs/app/models/agile/sprint.rb index 1c3e963ece1..02bd8a364a2 100644 --- a/modules/backlogs/app/models/agile/sprint.rb +++ b/modules/backlogs/app/models/agile/sprint.rb @@ -49,17 +49,17 @@ module Agile validates :start_date, presence: true validates :end_date, presence: true - validate :end_date_after_start_date + validate :validate_end_date_after_start_date - # TODO: sharing, work package journal + # TODO: sharing private - def end_date_after_start_date + def validate_end_date_after_start_date return if end_date.blank? || start_date.blank? if end_date < start_date - errors.add(:end_date, "must be after the start date") + errors.add(:end_date, :greater_than_or_equal_to_start_date) end end end diff --git a/modules/backlogs/spec/factories/sprint_factory.rb b/modules/backlogs/spec/factories/sprint_factory.rb index 161838d1efb..38876259218 100644 --- a/modules/backlogs/spec/factories/sprint_factory.rb +++ b/modules/backlogs/spec/factories/sprint_factory.rb @@ -33,4 +33,11 @@ FactoryBot.define do sharing { "none" } status { "open" } end + + factory :agile_sprint, class: "Agile::Sprint" do + sequence(:name) { |n| "Sprint #{n}" } + status { "in_planning" } + start_date { Time.zone.today } + end_date { Time.zone.today + 14.days } + end end diff --git a/modules/backlogs/spec/models/agile/sprint_spec.rb b/modules/backlogs/spec/models/agile/sprint_spec.rb new file mode 100644 index 00000000000..debd448694c --- /dev/null +++ b/modules/backlogs/spec/models/agile/sprint_spec.rb @@ -0,0 +1,84 @@ +# 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 Agile::Sprint do + subject(:sprint) do + described_class.new(name: "Sprint 1", + start_date: Time.zone.today, + end_date: Time.zone.today + 14.days) + end + + describe "validations" do + it { is_expected.to validate_presence_of(:name) } + it { is_expected.to validate_presence_of(:status) } + it { is_expected.to validate_presence_of(:start_date) } + it { is_expected.to validate_presence_of(:end_date) } + + it "validates end_date is after start_date" do + sprint.end_date = sprint.start_date - 1.day + expect(sprint).not_to be_valid + expect(sprint.errors[:end_date]).to include("must be after the start date") + end + end + + describe "associations" do + it { is_expected.to have_many(:work_packages).dependent(:nullify) } + end + + describe "enums" do + it "has status enum with correct values" do + expect(described_class.statuses.keys).to contain_exactly("in planning", "active", "completed") + end + end + + describe "default status" do + it "defaults to in_planning" do + expect(sprint.status).to eq("in planning") + end + end + + describe "work_package association" do + let(:project) { create(:project) } + let(:sprint) { create(:agile_sprint) } + let(:work_package) { create(:work_package, project:, sprint:) } + + it "can have work packages associated" do + expect(sprint.work_packages).to include(work_package) + end + + it "nullifies work_package sprint_id when destroyed" do + work_package_id = work_package.id + sprint.destroy! + expect(WorkPackage.find(work_package_id).sprint_id).to be_nil + end + end +end diff --git a/spec/models/work_package/sprint_journaling_spec.rb b/spec/models/work_package/sprint_journaling_spec.rb new file mode 100644 index 00000000000..564b8f44aaf --- /dev/null +++ b/spec/models/work_package/sprint_journaling_spec.rb @@ -0,0 +1,101 @@ +# 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 "WorkPackage sprint association journaling", # rubocop:disable RSpec/DescribeClass + with_settings: { journal_aggregation_time_minutes: 0 } do + shared_let(:project) { create(:project) } + shared_let(:user) { create(:user, member_with_permissions: { project => %i[view_work_packages edit_work_packages] }) } + shared_let(:sprint1) { create(:agile_sprint, name: "Sprint 1") } + shared_let(:sprint2) { create(:agile_sprint, name: "Sprint 2") } + shared_let(:work_package) { create(:work_package, project:) } + + before do + login_as(user) + end + + it "creates a journal entry when sprint is assigned" do + expect do + WorkPackages::UpdateService + .new(user:, model: work_package) + .call(sprint: sprint1) + end.to change(Journal::WorkPackageJournal, :count).by(1) + + last_journal = work_package.journals.last + expect(last_journal.details).to have_key("sprint_id") + expect(last_journal.details["sprint_id"]).to eq([nil, sprint1.id]) + end + + it "creates a journal entry when sprint is changed" do + work_package.update!(sprint: sprint1) + work_package.reload + + expect do + WorkPackages::UpdateService + .new(user:, model: work_package) + .call(sprint: sprint2) + end.to change(Journal::WorkPackageJournal, :count).by(1) + + last_journal = work_package.journals.last + expect(last_journal.details).to have_key("sprint_id") + expect(last_journal.details["sprint_id"]).to eq([sprint1.id, sprint2.id]) + end + + it "creates a journal entry when sprint is removed" do + work_package.update!(sprint: sprint1) + work_package.reload + + expect do + WorkPackages::UpdateService + .new(user:, model: work_package) + .call(sprint: nil) + end.to change(Journal::WorkPackageJournal, :count).by(1) + + last_journal = work_package.journals.last + expect(last_journal.details).to have_key("sprint_id") + expect(last_journal.details["sprint_id"]).to eq([sprint1.id, nil]) + end + + it "formats the sprint change in the journal" do + work_package.update!(sprint: sprint1) + work_package.reload + + WorkPackages::UpdateService + .new(user:, model: work_package) + .call(sprint: sprint2) + + last_journal = work_package.journals.last + formatted = last_journal.render_detail("sprint_id", no_html: true) + + expect(formatted).to include("Sprint 1") + expect(formatted).to include("Sprint 2") + end +end From eeff279e41b0518ddb67bf6e2f22fbd7e600a5e8 Mon Sep 17 00:00:00 2001 From: Tobias Dillmann Date: Wed, 4 Feb 2026 10:48:44 +0100 Subject: [PATCH 264/293] [#71248] Add draft for sharing Sprints --- modules/backlogs/app/models/agile/sprint.rb | 8 ++++--- .../migrate/20260202093215_create_sprints.rb | 2 ++ .../backlogs/spec/models/agile/sprint_spec.rb | 21 +++++++++++++++---- .../work_package/sprint_journaling_spec.rb | 4 ++-- 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/modules/backlogs/app/models/agile/sprint.rb b/modules/backlogs/app/models/agile/sprint.rb index 02bd8a364a2..c51282450fa 100644 --- a/modules/backlogs/app/models/agile/sprint.rb +++ b/modules/backlogs/app/models/agile/sprint.rb @@ -35,8 +35,8 @@ module Agile class Sprint < ApplicationRecord self.table_name = "sprints" + belongs_to :project, optional: false has_many :work_packages, dependent: :nullify - has_many :projects, through: :work_packages enum :status, { "in planning" => "in_planning", @@ -44,14 +44,16 @@ module Agile "completed" => "completed" }, default: "in_planning" + SPRINT_SHARINGS = %w(none descendants system).freeze + validates :name, presence: true validates :status, presence: true, inclusion: { in: statuses.keys } + validates :sharing, presence: true, inclusion: { in: SPRINT_SHARINGS } validates :start_date, presence: true validates :end_date, presence: true validate :validate_end_date_after_start_date - - # TODO: sharing + # TODO: validate sharing is set to an allowed value, e.g. only admins may share systemwide private diff --git a/modules/backlogs/db/migrate/20260202093215_create_sprints.rb b/modules/backlogs/db/migrate/20260202093215_create_sprints.rb index b12c212d09f..a9afe0e2d9c 100644 --- a/modules/backlogs/db/migrate/20260202093215_create_sprints.rb +++ b/modules/backlogs/db/migrate/20260202093215_create_sprints.rb @@ -35,6 +35,8 @@ class CreateSprints < ActiveRecord::Migration[8.0] t.string :status, null: false, default: "in_planning" t.date :start_date, null: false t.date :end_date, null: false + t.string :sharing, null: false, default: "none" + t.references :project t.timestamps end diff --git a/modules/backlogs/spec/models/agile/sprint_spec.rb b/modules/backlogs/spec/models/agile/sprint_spec.rb index debd448694c..047df7a358e 100644 --- a/modules/backlogs/spec/models/agile/sprint_spec.rb +++ b/modules/backlogs/spec/models/agile/sprint_spec.rb @@ -31,8 +31,11 @@ require "spec_helper" RSpec.describe Agile::Sprint do + let(:project) { create(:project) } + subject(:sprint) do described_class.new(name: "Sprint 1", + project:, start_date: Time.zone.today, end_date: Time.zone.today + 14.days) end @@ -43,15 +46,26 @@ RSpec.describe Agile::Sprint do it { is_expected.to validate_presence_of(:start_date) } it { is_expected.to validate_presence_of(:end_date) } - it "validates end_date is after start_date" do + it "validates status inclusion" do + expect(sprint).to allow_value("in planning").for(:status) + expect(sprint).to allow_value("active").for(:status) + expect(sprint).to allow_value("completed").for(:status) + end + + it "raises error for invalid status" do + expect { sprint.status = "invalid_status" }.to raise_error(ArgumentError, /'invalid_status' is not a valid status/) + end + + it "validates end_date is after or equal to start_date" do sprint.end_date = sprint.start_date - 1.day expect(sprint).not_to be_valid - expect(sprint.errors[:end_date]).to include("must be after the start date") + expect(sprint.errors[:end_date]).to include("must be greater than or equal to the start date.") end end describe "associations" do it { is_expected.to have_many(:work_packages).dependent(:nullify) } + it { is_expected.to belong_to(:project).required } end describe "enums" do @@ -67,8 +81,7 @@ RSpec.describe Agile::Sprint do end describe "work_package association" do - let(:project) { create(:project) } - let(:sprint) { create(:agile_sprint) } + let(:sprint) { create(:agile_sprint, project:) } let(:work_package) { create(:work_package, project:, sprint:) } it "can have work packages associated" do diff --git a/spec/models/work_package/sprint_journaling_spec.rb b/spec/models/work_package/sprint_journaling_spec.rb index 564b8f44aaf..f889abd12bc 100644 --- a/spec/models/work_package/sprint_journaling_spec.rb +++ b/spec/models/work_package/sprint_journaling_spec.rb @@ -34,8 +34,8 @@ RSpec.describe "WorkPackage sprint association journaling", # rubocop:disable RS with_settings: { journal_aggregation_time_minutes: 0 } do shared_let(:project) { create(:project) } shared_let(:user) { create(:user, member_with_permissions: { project => %i[view_work_packages edit_work_packages] }) } - shared_let(:sprint1) { create(:agile_sprint, name: "Sprint 1") } - shared_let(:sprint2) { create(:agile_sprint, name: "Sprint 2") } + shared_let(:sprint1) { create(:agile_sprint, name: "Sprint 1", project:) } + shared_let(:sprint2) { create(:agile_sprint, name: "Sprint 2", project:) } shared_let(:work_package) { create(:work_package, project:) } before do From c1b37623a582e4ee361748dbb1d17dd4f12a0521 Mon Sep 17 00:00:00 2001 From: Tobias Dillmann Date: Wed, 4 Feb 2026 17:05:54 +0100 Subject: [PATCH 265/293] [#71248] Refactor --- app/contracts/work_packages/base_contract.rb | 2 +- modules/backlogs/app/models/agile/sprint.rb | 6 ++++-- .../backlogs/spec/models/agile/sprint_spec.rb | 21 ++++++------------- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/app/contracts/work_packages/base_contract.rb b/app/contracts/work_packages/base_contract.rb index 94e0dd6bf3b..b26b1d1c309 100644 --- a/app/contracts/work_packages/base_contract.rb +++ b/app/contracts/work_packages/base_contract.rb @@ -136,7 +136,7 @@ module WorkPackages unless: -> { model.type&.replacement_pattern_defined_for?(:subject) } validates :subject, length: { maximum: 255 } - # TODO: add validation, check permission + # TODO: add validation, check permission (#71253) attribute :sprint_id validates :due_date, diff --git a/modules/backlogs/app/models/agile/sprint.rb b/modules/backlogs/app/models/agile/sprint.rb index c51282450fa..dbf8f0e9c50 100644 --- a/modules/backlogs/app/models/agile/sprint.rb +++ b/modules/backlogs/app/models/agile/sprint.rb @@ -42,7 +42,7 @@ module Agile "in planning" => "in_planning", "active" => "active", "completed" => "completed" - }, default: "in_planning" + }, default: "in_planning", validate: true SPRINT_SHARINGS = %w(none descendants system).freeze @@ -53,7 +53,9 @@ module Agile validates :end_date, presence: true validate :validate_end_date_after_start_date - # TODO: validate sharing is set to an allowed value, e.g. only admins may share systemwide + + # TODO: validate sharing is set to an allowed value, e.g. only admins may share systemwide (#71374, #71253) + # TODO: implement sharing logic once it has been defined (#71374) private diff --git a/modules/backlogs/spec/models/agile/sprint_spec.rb b/modules/backlogs/spec/models/agile/sprint_spec.rb index 047df7a358e..203ccffde3f 100644 --- a/modules/backlogs/spec/models/agile/sprint_spec.rb +++ b/modules/backlogs/spec/models/agile/sprint_spec.rb @@ -45,16 +45,7 @@ RSpec.describe Agile::Sprint do it { is_expected.to validate_presence_of(:status) } it { is_expected.to validate_presence_of(:start_date) } it { is_expected.to validate_presence_of(:end_date) } - - it "validates status inclusion" do - expect(sprint).to allow_value("in planning").for(:status) - expect(sprint).to allow_value("active").for(:status) - expect(sprint).to allow_value("completed").for(:status) - end - - it "raises error for invalid status" do - expect { sprint.status = "invalid_status" }.to raise_error(ArgumentError, /'invalid_status' is not a valid status/) - end + it { is_expected.to validate_inclusion_of(:status).in_array(described_class.statuses.keys) } it "validates end_date is after or equal to start_date" do sprint.end_date = sprint.start_date - 1.day @@ -63,11 +54,6 @@ RSpec.describe Agile::Sprint do end end - describe "associations" do - it { is_expected.to have_many(:work_packages).dependent(:nullify) } - it { is_expected.to belong_to(:project).required } - end - describe "enums" do it "has status enum with correct values" do expect(described_class.statuses.keys).to contain_exactly("in planning", "active", "completed") @@ -80,6 +66,11 @@ RSpec.describe Agile::Sprint do end end + describe "associations" do + it { is_expected.to have_many(:work_packages).dependent(:nullify) } + it { is_expected.to belong_to(:project).required } + end + describe "work_package association" do let(:sprint) { create(:agile_sprint, project:) } let(:work_package) { create(:work_package, project:, sprint:) } From 23cfb9b712780eed6a9f0bdca00b7e2dd4988dda Mon Sep 17 00:00:00 2001 From: Tobias Dillmann Date: Wed, 4 Feb 2026 18:56:52 +0100 Subject: [PATCH 266/293] [#71248] Only one active Sprint per project is allowed --- config/locales/en.yml | 1 + modules/backlogs/app/models/agile/sprint.rb | 14 +++++++ .../backlogs/patches/project_patch.rb | 3 ++ .../backlogs/spec/models/agile/sprint_spec.rb | 35 ++++++++++++++++++ modules/backlogs/spec/models/project_spec.rb | 37 +++++++++++++++++++ 5 files changed, 90 insertions(+) create mode 100644 modules/backlogs/spec/models/project_spec.rb diff --git a/config/locales/en.yml b/config/locales/en.yml index 68c794cc535..8a16bac79a1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1633,6 +1633,7 @@ en: not_available: "is not available due to a system configuration." not_deletable: "cannot be deleted." not_current_user: "is not the current user." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "is not a valid date." not_a_datetime: "is not a valid date time." diff --git a/modules/backlogs/app/models/agile/sprint.rb b/modules/backlogs/app/models/agile/sprint.rb index dbf8f0e9c50..ef3e7f1e29e 100644 --- a/modules/backlogs/app/models/agile/sprint.rb +++ b/modules/backlogs/app/models/agile/sprint.rb @@ -53,6 +53,7 @@ module Agile validates :end_date, presence: true validate :validate_end_date_after_start_date + validate :validate_only_one_active_sprint_per_project # TODO: validate sharing is set to an allowed value, e.g. only admins may share systemwide (#71374, #71253) # TODO: implement sharing logic once it has been defined (#71374) @@ -66,5 +67,18 @@ module Agile errors.add(:end_date, :greater_than_or_equal_to_start_date) end end + + def validate_only_one_active_sprint_per_project + return if !active? || project_id.blank? + + existing_active_sprint = self.class + .where(project_id:, status: "active") + .where.not(id:) + .exists? + + if existing_active_sprint + errors.add(:status, :only_one_active_sprint_allowed) + end + end end end diff --git a/modules/backlogs/lib/open_project/backlogs/patches/project_patch.rb b/modules/backlogs/lib/open_project/backlogs/patches/project_patch.rb index 067c66aee8e..f63de9c4b45 100644 --- a/modules/backlogs/lib/open_project/backlogs/patches/project_patch.rb +++ b/modules/backlogs/lib/open_project/backlogs/patches/project_patch.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH @@ -30,6 +32,7 @@ module OpenProject::Backlogs::Patches::ProjectPatch def self.included(base) base.class_eval do has_and_belongs_to_many :done_statuses, join_table: :done_statuses_for_project, class_name: "::Status" + has_many :sprints, class_name: "Agile::Sprint", dependent: :destroy include InstanceMethods end diff --git a/modules/backlogs/spec/models/agile/sprint_spec.rb b/modules/backlogs/spec/models/agile/sprint_spec.rb index 203ccffde3f..aa5534168b2 100644 --- a/modules/backlogs/spec/models/agile/sprint_spec.rb +++ b/modules/backlogs/spec/models/agile/sprint_spec.rb @@ -52,6 +52,41 @@ RSpec.describe Agile::Sprint do expect(sprint).not_to be_valid expect(sprint.errors[:end_date]).to include("must be greater than or equal to the start date.") end + + context "with active sprint validation" do + it "allows one active sprint per project" do + sprint.status = "active" + expect(sprint).to be_valid + end + + it "prevents multiple active sprints in the same project" do + create(:agile_sprint, project:, status: "active") + sprint.status = "active" + expect(sprint).not_to be_valid + expect(sprint.errors[:status]).to include("only one active sprint is allowed per project.") + end + + it "allows multiple active sprints in different projects" do + other_project = create(:project) + create(:agile_sprint, project: other_project, status: "active") + sprint.status = "active" + expect(sprint).to be_valid + end + + it "allows updating an existing active sprint" do + sprint.status = "active" + sprint.save! + sprint.name = "Updated Sprint" + expect(sprint).to be_valid + end + + it "allows multiple non-active sprints in the same project" do + create(:agile_sprint, project:, status: "completed") + create(:agile_sprint, project:, status: "in planning") + sprint.status = "in planning" + expect(sprint).to be_valid + end + end end describe "enums" do diff --git a/modules/backlogs/spec/models/project_spec.rb b/modules/backlogs/spec/models/project_spec.rb new file mode 100644 index 00000000000..0296e6872ed --- /dev/null +++ b/modules/backlogs/spec/models/project_spec.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. +#++ + +require "spec_helper" + +RSpec.describe Project do + describe "associations" do + it { is_expected.to have_many(:sprints).class_name("Agile::Sprint").dependent(:destroy) } + end +end From e912b74d52086d66abe47c554598791762bfa8d4 Mon Sep 17 00:00:00 2001 From: Tobias Dillmann Date: Wed, 4 Feb 2026 19:14:39 +0100 Subject: [PATCH 267/293] [#71248] Do you wanna build a snowman? --- .../lib/open_project/backlogs/patches/work_package_patch.rb | 2 ++ modules/backlogs/spec/factories/sprint_factory.rb | 2 ++ 2 files changed, 4 insertions(+) diff --git a/modules/backlogs/lib/open_project/backlogs/patches/work_package_patch.rb b/modules/backlogs/lib/open_project/backlogs/patches/work_package_patch.rb index 7d8bade7c75..7e959a3d486 100644 --- a/modules/backlogs/lib/open_project/backlogs/patches/work_package_patch.rb +++ b/modules/backlogs/lib/open_project/backlogs/patches/work_package_patch.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH diff --git a/modules/backlogs/spec/factories/sprint_factory.rb b/modules/backlogs/spec/factories/sprint_factory.rb index 38876259218..39010be123f 100644 --- a/modules/backlogs/spec/factories/sprint_factory.rb +++ b/modules/backlogs/spec/factories/sprint_factory.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH From 3d71c9c533d23e873ad9b61ce449eb6dbaf7fff7 Mon Sep 17 00:00:00 2001 From: Tobias Dillmann Date: Mon, 9 Feb 2026 08:51:06 +0100 Subject: [PATCH 268/293] [#71248] Remove unnecessary optional option --- modules/backlogs/app/models/agile/sprint.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/backlogs/app/models/agile/sprint.rb b/modules/backlogs/app/models/agile/sprint.rb index ef3e7f1e29e..6e1850931c1 100644 --- a/modules/backlogs/app/models/agile/sprint.rb +++ b/modules/backlogs/app/models/agile/sprint.rb @@ -35,7 +35,7 @@ module Agile class Sprint < ApplicationRecord self.table_name = "sprints" - belongs_to :project, optional: false + belongs_to :project has_many :work_packages, dependent: :nullify enum :status, { From b7840c10ddb1d47095910e675250189383c8e9fe Mon Sep 17 00:00:00 2001 From: Tobias Dillmann Date: Mon, 9 Feb 2026 09:04:30 +0100 Subject: [PATCH 269/293] [#71248] Proper project key reference options --- modules/backlogs/db/migrate/20260202093215_create_sprints.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/backlogs/db/migrate/20260202093215_create_sprints.rb b/modules/backlogs/db/migrate/20260202093215_create_sprints.rb index a9afe0e2d9c..f5d26217c20 100644 --- a/modules/backlogs/db/migrate/20260202093215_create_sprints.rb +++ b/modules/backlogs/db/migrate/20260202093215_create_sprints.rb @@ -36,7 +36,7 @@ class CreateSprints < ActiveRecord::Migration[8.0] t.date :start_date, null: false t.date :end_date, null: false t.string :sharing, null: false, default: "none" - t.references :project + t.references :project, null: false, foreign_key: true t.timestamps end From 86977ff8ef4dcd0db8a210145a6425d76cc33c7b Mon Sep 17 00:00:00 2001 From: Tobias Dillmann Date: Mon, 9 Feb 2026 09:10:55 +0100 Subject: [PATCH 270/293] [#71248] Remove superfluous enum validation --- modules/backlogs/app/models/agile/sprint.rb | 2 +- modules/backlogs/spec/models/agile/sprint_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/backlogs/app/models/agile/sprint.rb b/modules/backlogs/app/models/agile/sprint.rb index 6e1850931c1..cdaa668abf0 100644 --- a/modules/backlogs/app/models/agile/sprint.rb +++ b/modules/backlogs/app/models/agile/sprint.rb @@ -47,7 +47,7 @@ module Agile SPRINT_SHARINGS = %w(none descendants system).freeze validates :name, presence: true - validates :status, presence: true, inclusion: { in: statuses.keys } + validates :project, presence: true validates :sharing, presence: true, inclusion: { in: SPRINT_SHARINGS } validates :start_date, presence: true validates :end_date, presence: true diff --git a/modules/backlogs/spec/models/agile/sprint_spec.rb b/modules/backlogs/spec/models/agile/sprint_spec.rb index aa5534168b2..952d8472edb 100644 --- a/modules/backlogs/spec/models/agile/sprint_spec.rb +++ b/modules/backlogs/spec/models/agile/sprint_spec.rb @@ -42,9 +42,9 @@ RSpec.describe Agile::Sprint do describe "validations" do it { is_expected.to validate_presence_of(:name) } - it { is_expected.to validate_presence_of(:status) } it { is_expected.to validate_presence_of(:start_date) } it { is_expected.to validate_presence_of(:end_date) } + it { is_expected.to validate_presence_of(:project) } it { is_expected.to validate_inclusion_of(:status).in_array(described_class.statuses.keys) } it "validates end_date is after or equal to start_date" do @@ -103,7 +103,7 @@ RSpec.describe Agile::Sprint do describe "associations" do it { is_expected.to have_many(:work_packages).dependent(:nullify) } - it { is_expected.to belong_to(:project).required } + it { is_expected.to belong_to(:project) } end describe "work_package association" do From 5b73992ca51f79cfb7ff8b2356d710d43000bc58 Mon Sep 17 00:00:00 2001 From: Tobias Dillmann Date: Mon, 9 Feb 2026 09:52:28 +0100 Subject: [PATCH 271/293] [#71248] Add active sprint rule TODO --- modules/backlogs/app/models/agile/sprint.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/backlogs/app/models/agile/sprint.rb b/modules/backlogs/app/models/agile/sprint.rb index cdaa668abf0..a352e7bc042 100644 --- a/modules/backlogs/app/models/agile/sprint.rb +++ b/modules/backlogs/app/models/agile/sprint.rb @@ -68,6 +68,9 @@ module Agile end end + # TODO: consider moving this validation to the database level to ensure data integrity. + # Doing this in Rails can lead to race conditions. Revisit this topic once the sharing + # logic has been fully specified. def validate_only_one_active_sprint_per_project return if !active? || project_id.blank? From e3c843a3637c1b5337df50ae389c7aa52fa7eb0c Mon Sep 17 00:00:00 2001 From: Tobias Dillmann Date: Mon, 9 Feb 2026 10:33:14 +0100 Subject: [PATCH 272/293] [#71248] Use method safe keys for enum, add share specs --- config/locales/en.yml | 4 ++- modules/backlogs/app/models/agile/sprint.rb | 6 ++-- .../backlogs/spec/models/agile/sprint_spec.rb | 29 ++++++++++++++----- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 8a16bac79a1..e962b7ed249 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1258,6 +1258,9 @@ en: activerecord: attributes: + agile/sprint: + sharing: "Sharing" + end_date: "End date" announcements: show_until: "Display until" attachment: @@ -2162,7 +2165,6 @@ en: estimated_hours: "Work" estimated_time: "Work" email: "Email" - end_date: "End date" entity_type: "Entity" expires_at: "Expires on" firstname: "First name" diff --git a/modules/backlogs/app/models/agile/sprint.rb b/modules/backlogs/app/models/agile/sprint.rb index a352e7bc042..b4086c83de4 100644 --- a/modules/backlogs/app/models/agile/sprint.rb +++ b/modules/backlogs/app/models/agile/sprint.rb @@ -39,9 +39,9 @@ module Agile has_many :work_packages, dependent: :nullify enum :status, { - "in planning" => "in_planning", - "active" => "active", - "completed" => "completed" + in_planning: "in_planning", + active: "active", + completed: "completed" }, default: "in_planning", validate: true SPRINT_SHARINGS = %w(none descendants system).freeze diff --git a/modules/backlogs/spec/models/agile/sprint_spec.rb b/modules/backlogs/spec/models/agile/sprint_spec.rb index 952d8472edb..890fc7c2e8f 100644 --- a/modules/backlogs/spec/models/agile/sprint_spec.rb +++ b/modules/backlogs/spec/models/agile/sprint_spec.rb @@ -46,6 +46,7 @@ RSpec.describe Agile::Sprint do it { is_expected.to validate_presence_of(:end_date) } it { is_expected.to validate_presence_of(:project) } it { is_expected.to validate_inclusion_of(:status).in_array(described_class.statuses.keys) } + it { is_expected.to validate_inclusion_of(:sharing).in_array(described_class::SPRINT_SHARINGS) } it "validates end_date is after or equal to start_date" do sprint.end_date = sprint.start_date - 1.day @@ -82,8 +83,8 @@ RSpec.describe Agile::Sprint do it "allows multiple non-active sprints in the same project" do create(:agile_sprint, project:, status: "completed") - create(:agile_sprint, project:, status: "in planning") - sprint.status = "in planning" + create(:agile_sprint, project:, status: "in_planning") + sprint.status = "in_planning" expect(sprint).to be_valid end end @@ -91,13 +92,27 @@ RSpec.describe Agile::Sprint do describe "enums" do it "has status enum with correct values" do - expect(described_class.statuses.keys).to contain_exactly("in planning", "active", "completed") + expect(described_class.statuses.keys).to contain_exactly("in_planning", "active", "completed") end - end - describe "default status" do - it "defaults to in_planning" do - expect(sprint.status).to eq("in planning") + it "status defaults to in_planning" do + expect(sprint.status).to eq("in_planning") + end + + it "allows sharing settings" do + %w[none descendants system].each do |sharing| + sprint.sharing = sharing + expect(sprint).to be_valid + end + + %w[invalid_value another].each do |invalid_sharing| + sprint.sharing = invalid_sharing + expect(sprint).not_to be_valid + end + end + + it "sharing defaults to none" do + expect(sprint.sharing).to eq("none") end end From e99f56d3284198b3219b7e3bc05b9f947aaa52c0 Mon Sep 17 00:00:00 2001 From: Tobias Dillmann Date: Mon, 9 Feb 2026 13:28:47 +0100 Subject: [PATCH 273/293] [#71248] Use allow_values --- modules/backlogs/spec/models/agile/sprint_spec.rb | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/modules/backlogs/spec/models/agile/sprint_spec.rb b/modules/backlogs/spec/models/agile/sprint_spec.rb index 890fc7c2e8f..906e5fd0634 100644 --- a/modules/backlogs/spec/models/agile/sprint_spec.rb +++ b/modules/backlogs/spec/models/agile/sprint_spec.rb @@ -100,15 +100,8 @@ RSpec.describe Agile::Sprint do end it "allows sharing settings" do - %w[none descendants system].each do |sharing| - sprint.sharing = sharing - expect(sprint).to be_valid - end - - %w[invalid_value another].each do |invalid_sharing| - sprint.sharing = invalid_sharing - expect(sprint).not_to be_valid - end + expect(sprint).to allow_values(*%w[none descendants system]).for(:sharing) + expect(sprint).not_to allow_value(*%w[invalid_value hierarchy tree]).for(:sharing) end it "sharing defaults to none" do From 97bcc6e2c75a1a77021ac47eecab1d411abb052e Mon Sep 17 00:00:00 2001 From: Tobias Dillmann Date: Mon, 9 Feb 2026 13:34:24 +0100 Subject: [PATCH 274/293] [#71248] Use built-in comparison validator --- modules/backlogs/app/models/agile/sprint.rb | 13 +++---------- modules/backlogs/spec/models/agile/sprint_spec.rb | 2 +- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/modules/backlogs/app/models/agile/sprint.rb b/modules/backlogs/app/models/agile/sprint.rb index b4086c83de4..5c8391a786e 100644 --- a/modules/backlogs/app/models/agile/sprint.rb +++ b/modules/backlogs/app/models/agile/sprint.rb @@ -50,9 +50,10 @@ module Agile validates :project, presence: true validates :sharing, presence: true, inclusion: { in: SPRINT_SHARINGS } validates :start_date, presence: true - validates :end_date, presence: true + validates :end_date, + presence: true, + comparison: { greater_than_or_equal_to: :start_date } - validate :validate_end_date_after_start_date validate :validate_only_one_active_sprint_per_project # TODO: validate sharing is set to an allowed value, e.g. only admins may share systemwide (#71374, #71253) @@ -60,14 +61,6 @@ module Agile private - def validate_end_date_after_start_date - return if end_date.blank? || start_date.blank? - - if end_date < start_date - errors.add(:end_date, :greater_than_or_equal_to_start_date) - end - end - # TODO: consider moving this validation to the database level to ensure data integrity. # Doing this in Rails can lead to race conditions. Revisit this topic once the sharing # logic has been fully specified. diff --git a/modules/backlogs/spec/models/agile/sprint_spec.rb b/modules/backlogs/spec/models/agile/sprint_spec.rb index 906e5fd0634..8944e6faff4 100644 --- a/modules/backlogs/spec/models/agile/sprint_spec.rb +++ b/modules/backlogs/spec/models/agile/sprint_spec.rb @@ -51,7 +51,7 @@ RSpec.describe Agile::Sprint do it "validates end_date is after or equal to start_date" do sprint.end_date = sprint.start_date - 1.day expect(sprint).not_to be_valid - expect(sprint.errors[:end_date]).to include("must be greater than or equal to the start date.") + expect(sprint.errors[:end_date]).to include(/must be greater than or equal to/) end context "with active sprint validation" do From 46da771432fb500263b60945105e36a8a84d56eb Mon Sep 17 00:00:00 2001 From: Tobias Dillmann Date: Mon, 9 Feb 2026 15:01:47 +0100 Subject: [PATCH 275/293] [#71248] Add constraint for end date --- modules/backlogs/db/migrate/20260202093215_create_sprints.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/backlogs/db/migrate/20260202093215_create_sprints.rb b/modules/backlogs/db/migrate/20260202093215_create_sprints.rb index f5d26217c20..2fabf2cda99 100644 --- a/modules/backlogs/db/migrate/20260202093215_create_sprints.rb +++ b/modules/backlogs/db/migrate/20260202093215_create_sprints.rb @@ -39,6 +39,8 @@ class CreateSprints < ActiveRecord::Migration[8.0] t.references :project, null: false, foreign_key: true t.timestamps + + t.check_constraint "end_date >= start_date", name: "sprint_end_date_after_start_date" end end end From fc1d596e637d2ea854bcd41a9948290b914f9546 Mon Sep 17 00:00:00 2001 From: Tobias Dillmann Date: Wed, 11 Feb 2026 11:28:33 +0100 Subject: [PATCH 276/293] [#71248] Improve database schema by using foreign keys --- .../migrate/20260202150252_add_sprint_id_to_work_packages.rb | 4 +--- .../20260202150253_add_sprint_id_to_work_package_journals.rb | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/backlogs/db/migrate/20260202150252_add_sprint_id_to_work_packages.rb b/modules/backlogs/db/migrate/20260202150252_add_sprint_id_to_work_packages.rb index ff3094cabaa..d0582c8ae21 100644 --- a/modules/backlogs/db/migrate/20260202150252_add_sprint_id_to_work_packages.rb +++ b/modules/backlogs/db/migrate/20260202150252_add_sprint_id_to_work_packages.rb @@ -30,8 +30,6 @@ class AddSprintIdToWorkPackages < ActiveRecord::Migration[8.0] def change - add_column :work_packages, :sprint_id, :integer, default: nil, null: true - - add_index :work_packages, :sprint_id + add_reference :work_packages, :sprint, foreign_key: { on_delete: :nullify } end end diff --git a/modules/backlogs/db/migrate/20260202150253_add_sprint_id_to_work_package_journals.rb b/modules/backlogs/db/migrate/20260202150253_add_sprint_id_to_work_package_journals.rb index ec0dde79862..89e769d374f 100644 --- a/modules/backlogs/db/migrate/20260202150253_add_sprint_id_to_work_package_journals.rb +++ b/modules/backlogs/db/migrate/20260202150253_add_sprint_id_to_work_package_journals.rb @@ -30,6 +30,6 @@ class AddSprintIdToWorkPackageJournals < ActiveRecord::Migration[8.0] def change - add_column :work_package_journals, :sprint_id, :integer, default: nil, null: true + add_reference :work_package_journals, :sprint, foreign_key: { on_delete: :nullify } end end From e0ece27b0e969afc9cffd94642162add4a4c11c9 Mon Sep 17 00:00:00 2001 From: Tobias Dillmann Date: Wed, 11 Feb 2026 14:44:11 +0100 Subject: [PATCH 277/293] [#71248] Rename end_date to finish_date --- config/locales/en.yml | 2 +- modules/backlogs/app/models/agile/sprint.rb | 2 +- .../db/migrate/20260202093215_create_sprints.rb | 4 ++-- modules/backlogs/spec/factories/sprint_factory.rb | 2 +- modules/backlogs/spec/models/agile/sprint_spec.rb | 10 +++++----- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index e962b7ed249..bad09852a01 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1260,7 +1260,7 @@ en: attributes: agile/sprint: sharing: "Sharing" - end_date: "End date" + finish_date: "End date" announcements: show_until: "Display until" attachment: diff --git a/modules/backlogs/app/models/agile/sprint.rb b/modules/backlogs/app/models/agile/sprint.rb index 5c8391a786e..dda13f3b4bb 100644 --- a/modules/backlogs/app/models/agile/sprint.rb +++ b/modules/backlogs/app/models/agile/sprint.rb @@ -50,7 +50,7 @@ module Agile validates :project, presence: true validates :sharing, presence: true, inclusion: { in: SPRINT_SHARINGS } validates :start_date, presence: true - validates :end_date, + validates :finish_date, presence: true, comparison: { greater_than_or_equal_to: :start_date } diff --git a/modules/backlogs/db/migrate/20260202093215_create_sprints.rb b/modules/backlogs/db/migrate/20260202093215_create_sprints.rb index 2fabf2cda99..9013c2f8547 100644 --- a/modules/backlogs/db/migrate/20260202093215_create_sprints.rb +++ b/modules/backlogs/db/migrate/20260202093215_create_sprints.rb @@ -34,13 +34,13 @@ class CreateSprints < ActiveRecord::Migration[8.0] t.string :name, null: false t.string :status, null: false, default: "in_planning" t.date :start_date, null: false - t.date :end_date, null: false + t.date :finish_date, null: false t.string :sharing, null: false, default: "none" t.references :project, null: false, foreign_key: true t.timestamps - t.check_constraint "end_date >= start_date", name: "sprint_end_date_after_start_date" + t.check_constraint "finish_date >= start_date", name: "sprint_finish_date_after_start_date" end end end diff --git a/modules/backlogs/spec/factories/sprint_factory.rb b/modules/backlogs/spec/factories/sprint_factory.rb index 39010be123f..4bd86946620 100644 --- a/modules/backlogs/spec/factories/sprint_factory.rb +++ b/modules/backlogs/spec/factories/sprint_factory.rb @@ -40,6 +40,6 @@ FactoryBot.define do sequence(:name) { |n| "Sprint #{n}" } status { "in_planning" } start_date { Time.zone.today } - end_date { Time.zone.today + 14.days } + finish_date { Time.zone.today + 14.days } end end diff --git a/modules/backlogs/spec/models/agile/sprint_spec.rb b/modules/backlogs/spec/models/agile/sprint_spec.rb index 8944e6faff4..bf00b256ce3 100644 --- a/modules/backlogs/spec/models/agile/sprint_spec.rb +++ b/modules/backlogs/spec/models/agile/sprint_spec.rb @@ -37,21 +37,21 @@ RSpec.describe Agile::Sprint do described_class.new(name: "Sprint 1", project:, start_date: Time.zone.today, - end_date: Time.zone.today + 14.days) + finish_date: Time.zone.today + 14.days) end describe "validations" do it { is_expected.to validate_presence_of(:name) } it { is_expected.to validate_presence_of(:start_date) } - it { is_expected.to validate_presence_of(:end_date) } + it { is_expected.to validate_presence_of(:finish_date) } it { is_expected.to validate_presence_of(:project) } it { is_expected.to validate_inclusion_of(:status).in_array(described_class.statuses.keys) } it { is_expected.to validate_inclusion_of(:sharing).in_array(described_class::SPRINT_SHARINGS) } - it "validates end_date is after or equal to start_date" do - sprint.end_date = sprint.start_date - 1.day + it "validates finish_date is after or equal to start_date" do + sprint.finish_date = sprint.start_date - 1.day expect(sprint).not_to be_valid - expect(sprint.errors[:end_date]).to include(/must be greater than or equal to/) + expect(sprint.errors[:finish_date]).to include(/must be greater than or equal to/) end context "with active sprint validation" do From 3e089d7cdc7a0fd4bc3a9e4ee1d5ac3a6a987a0e Mon Sep 17 00:00:00 2001 From: OpenProject Actions CI Date: Thu, 12 Feb 2026 03:58:30 +0000 Subject: [PATCH 278/293] update locales from crowdin [ci skip] --- config/locales/crowdin/af.yml | 5 +++++ config/locales/crowdin/ar.yml | 5 +++++ config/locales/crowdin/az.yml | 5 +++++ config/locales/crowdin/be.yml | 5 +++++ config/locales/crowdin/bg.yml | 5 +++++ config/locales/crowdin/ca.yml | 5 +++++ config/locales/crowdin/ckb-IR.yml | 5 +++++ config/locales/crowdin/cs.yml | 5 +++++ config/locales/crowdin/da.yml | 5 +++++ config/locales/crowdin/de.yml | 5 +++++ config/locales/crowdin/el.yml | 5 +++++ config/locales/crowdin/eo.yml | 5 +++++ config/locales/crowdin/es.yml | 5 +++++ config/locales/crowdin/et.yml | 5 +++++ config/locales/crowdin/eu.yml | 5 +++++ config/locales/crowdin/fa.yml | 5 +++++ config/locales/crowdin/fi.yml | 5 +++++ config/locales/crowdin/fil.yml | 5 +++++ config/locales/crowdin/fr.yml | 5 +++++ config/locales/crowdin/he.yml | 5 +++++ config/locales/crowdin/hi.yml | 5 +++++ config/locales/crowdin/hr.yml | 5 +++++ config/locales/crowdin/hu.yml | 5 +++++ config/locales/crowdin/id.yml | 5 +++++ config/locales/crowdin/it.yml | 5 +++++ config/locales/crowdin/ja.yml | 5 +++++ config/locales/crowdin/ka.yml | 5 +++++ config/locales/crowdin/kk.yml | 5 +++++ config/locales/crowdin/ko.yml | 5 +++++ config/locales/crowdin/lt.yml | 5 +++++ config/locales/crowdin/lv.yml | 5 +++++ config/locales/crowdin/mn.yml | 5 +++++ config/locales/crowdin/ms.yml | 5 +++++ config/locales/crowdin/ne.yml | 5 +++++ config/locales/crowdin/nl.yml | 5 +++++ config/locales/crowdin/no.yml | 5 +++++ config/locales/crowdin/pl.yml | 5 +++++ config/locales/crowdin/pt-BR.yml | 5 +++++ config/locales/crowdin/pt-PT.yml | 5 +++++ config/locales/crowdin/ro.yml | 5 +++++ config/locales/crowdin/ru.yml | 5 +++++ config/locales/crowdin/rw.yml | 5 +++++ config/locales/crowdin/si.yml | 5 +++++ config/locales/crowdin/sk.yml | 5 +++++ config/locales/crowdin/sl.yml | 5 +++++ config/locales/crowdin/sr.yml | 5 +++++ config/locales/crowdin/sv.yml | 5 +++++ config/locales/crowdin/th.yml | 5 +++++ config/locales/crowdin/tr.yml | 5 +++++ config/locales/crowdin/uk.yml | 5 +++++ config/locales/crowdin/uz.yml | 5 +++++ config/locales/crowdin/vi.yml | 5 +++++ config/locales/crowdin/zh-CN.yml | 5 +++++ config/locales/crowdin/zh-TW.yml | 5 +++++ modules/backlogs/config/locales/crowdin/af.yml | 4 ---- modules/backlogs/config/locales/crowdin/ar.yml | 4 ---- modules/backlogs/config/locales/crowdin/az.yml | 4 ---- modules/backlogs/config/locales/crowdin/be.yml | 4 ---- modules/backlogs/config/locales/crowdin/bg.yml | 4 ---- modules/backlogs/config/locales/crowdin/ca.yml | 4 ---- modules/backlogs/config/locales/crowdin/ckb-IR.yml | 4 ---- modules/backlogs/config/locales/crowdin/cs.yml | 4 ---- modules/backlogs/config/locales/crowdin/da.yml | 4 ---- modules/backlogs/config/locales/crowdin/de.yml | 4 ---- modules/backlogs/config/locales/crowdin/el.yml | 4 ---- modules/backlogs/config/locales/crowdin/eo.yml | 4 ---- modules/backlogs/config/locales/crowdin/es.yml | 4 ---- modules/backlogs/config/locales/crowdin/et.yml | 4 ---- modules/backlogs/config/locales/crowdin/eu.yml | 4 ---- modules/backlogs/config/locales/crowdin/fa.yml | 4 ---- modules/backlogs/config/locales/crowdin/fi.yml | 4 ---- modules/backlogs/config/locales/crowdin/fil.yml | 4 ---- modules/backlogs/config/locales/crowdin/fr.yml | 4 ---- modules/backlogs/config/locales/crowdin/he.yml | 4 ---- modules/backlogs/config/locales/crowdin/hi.yml | 4 ---- modules/backlogs/config/locales/crowdin/hr.yml | 4 ---- modules/backlogs/config/locales/crowdin/hu.yml | 4 ---- modules/backlogs/config/locales/crowdin/id.yml | 4 ---- modules/backlogs/config/locales/crowdin/it.yml | 4 ---- modules/backlogs/config/locales/crowdin/ja.yml | 4 ---- modules/backlogs/config/locales/crowdin/ka.yml | 4 ---- modules/backlogs/config/locales/crowdin/kk.yml | 4 ---- modules/backlogs/config/locales/crowdin/ko.yml | 4 ---- modules/backlogs/config/locales/crowdin/lt.yml | 4 ---- modules/backlogs/config/locales/crowdin/lv.yml | 4 ---- modules/backlogs/config/locales/crowdin/mn.yml | 4 ---- modules/backlogs/config/locales/crowdin/ms.yml | 4 ---- modules/backlogs/config/locales/crowdin/ne.yml | 4 ---- modules/backlogs/config/locales/crowdin/nl.yml | 4 ---- modules/backlogs/config/locales/crowdin/no.yml | 4 ---- modules/backlogs/config/locales/crowdin/pl.yml | 4 ---- modules/backlogs/config/locales/crowdin/pt-BR.yml | 4 ---- modules/backlogs/config/locales/crowdin/pt-PT.yml | 4 ---- modules/backlogs/config/locales/crowdin/ro.yml | 4 ---- modules/backlogs/config/locales/crowdin/ru.yml | 4 ---- modules/backlogs/config/locales/crowdin/rw.yml | 4 ---- modules/backlogs/config/locales/crowdin/si.yml | 4 ---- modules/backlogs/config/locales/crowdin/sk.yml | 4 ---- modules/backlogs/config/locales/crowdin/sl.yml | 4 ---- modules/backlogs/config/locales/crowdin/sr.yml | 4 ---- modules/backlogs/config/locales/crowdin/sv.yml | 4 ---- modules/backlogs/config/locales/crowdin/th.yml | 4 ---- modules/backlogs/config/locales/crowdin/tr.yml | 4 ---- modules/backlogs/config/locales/crowdin/uk.yml | 4 ---- modules/backlogs/config/locales/crowdin/uz.yml | 4 ---- modules/backlogs/config/locales/crowdin/vi.yml | 4 ---- modules/backlogs/config/locales/crowdin/zh-CN.yml | 4 ---- modules/backlogs/config/locales/crowdin/zh-TW.yml | 4 ---- modules/grids/config/locales/crowdin/js-af.yml | 2 -- modules/grids/config/locales/crowdin/js-ar.yml | 2 -- modules/grids/config/locales/crowdin/js-az.yml | 2 -- modules/grids/config/locales/crowdin/js-be.yml | 2 -- modules/grids/config/locales/crowdin/js-bg.yml | 2 -- modules/grids/config/locales/crowdin/js-ca.yml | 2 -- modules/grids/config/locales/crowdin/js-ckb-IR.yml | 2 -- modules/grids/config/locales/crowdin/js-cs.yml | 2 -- modules/grids/config/locales/crowdin/js-da.yml | 2 -- modules/grids/config/locales/crowdin/js-de.yml | 2 -- modules/grids/config/locales/crowdin/js-el.yml | 2 -- modules/grids/config/locales/crowdin/js-eo.yml | 2 -- modules/grids/config/locales/crowdin/js-es.yml | 2 -- modules/grids/config/locales/crowdin/js-et.yml | 2 -- modules/grids/config/locales/crowdin/js-eu.yml | 2 -- modules/grids/config/locales/crowdin/js-fa.yml | 2 -- modules/grids/config/locales/crowdin/js-fi.yml | 2 -- modules/grids/config/locales/crowdin/js-fil.yml | 2 -- modules/grids/config/locales/crowdin/js-fr.yml | 2 -- modules/grids/config/locales/crowdin/js-he.yml | 2 -- modules/grids/config/locales/crowdin/js-hi.yml | 2 -- modules/grids/config/locales/crowdin/js-hr.yml | 2 -- modules/grids/config/locales/crowdin/js-hu.yml | 2 -- modules/grids/config/locales/crowdin/js-id.yml | 2 -- modules/grids/config/locales/crowdin/js-it.yml | 2 -- modules/grids/config/locales/crowdin/js-ja.yml | 2 -- modules/grids/config/locales/crowdin/js-ka.yml | 2 -- modules/grids/config/locales/crowdin/js-kk.yml | 2 -- modules/grids/config/locales/crowdin/js-ko.yml | 2 -- modules/grids/config/locales/crowdin/js-lt.yml | 2 -- modules/grids/config/locales/crowdin/js-lv.yml | 2 -- modules/grids/config/locales/crowdin/js-mn.yml | 2 -- modules/grids/config/locales/crowdin/js-ms.yml | 2 -- modules/grids/config/locales/crowdin/js-ne.yml | 2 -- modules/grids/config/locales/crowdin/js-nl.yml | 2 -- modules/grids/config/locales/crowdin/js-no.yml | 2 -- modules/grids/config/locales/crowdin/js-pl.yml | 2 -- modules/grids/config/locales/crowdin/js-pt-BR.yml | 2 -- modules/grids/config/locales/crowdin/js-pt-PT.yml | 2 -- modules/grids/config/locales/crowdin/js-ro.yml | 2 -- modules/grids/config/locales/crowdin/js-ru.yml | 2 -- modules/grids/config/locales/crowdin/js-rw.yml | 2 -- modules/grids/config/locales/crowdin/js-si.yml | 2 -- modules/grids/config/locales/crowdin/js-sk.yml | 2 -- modules/grids/config/locales/crowdin/js-sl.yml | 2 -- modules/grids/config/locales/crowdin/js-sr.yml | 2 -- modules/grids/config/locales/crowdin/js-sv.yml | 2 -- modules/grids/config/locales/crowdin/js-th.yml | 2 -- modules/grids/config/locales/crowdin/js-tr.yml | 2 -- modules/grids/config/locales/crowdin/js-uk.yml | 2 -- modules/grids/config/locales/crowdin/js-uz.yml | 2 -- modules/grids/config/locales/crowdin/js-vi.yml | 2 -- modules/grids/config/locales/crowdin/js-zh-CN.yml | 2 -- modules/grids/config/locales/crowdin/js-zh-TW.yml | 2 -- 162 files changed, 270 insertions(+), 324 deletions(-) diff --git a/config/locales/crowdin/af.yml b/config/locales/crowdin/af.yml index 5ab33fb6dab..52217420d47 100644 --- a/config/locales/crowdin/af.yml +++ b/config/locales/crowdin/af.yml @@ -1163,6 +1163,9 @@ af: dependencies: "Dependencies" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Vertoon tot" attachment: @@ -1537,6 +1540,7 @@ af: not_available: "is not available due to a system configuration." not_deletable: "cannot be deleted." not_current_user: "is not the current user." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "is not a valid date." not_a_datetime: "is not a valid date time." @@ -2098,6 +2102,7 @@ af: role: "Rol" roles: "Rolle" search: "Search" + sprint: "Sprint" start_date: "Begindatum" status: "Status" state: "State" diff --git a/config/locales/crowdin/ar.yml b/config/locales/crowdin/ar.yml index 7ebaa76f734..a2eba1a8d85 100644 --- a/config/locales/crowdin/ar.yml +++ b/config/locales/crowdin/ar.yml @@ -1199,6 +1199,9 @@ ar: dependencies: "الاعتماديات" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "أظهِر حتّى" attachment: @@ -1573,6 +1576,7 @@ ar: not_available: "is not available due to a system configuration." not_deletable: "cannot be deleted." not_current_user: "is not the current user." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "is not a valid date." not_a_datetime: "is not a valid date time." @@ -2210,6 +2214,7 @@ ar: role: "الدور" roles: "دور" search: "البحث" + sprint: "Sprint" start_date: "تاريخ البدء" status: "الحالة" state: "State" diff --git a/config/locales/crowdin/az.yml b/config/locales/crowdin/az.yml index 2e93bf4c79b..9d36a2cee2e 100644 --- a/config/locales/crowdin/az.yml +++ b/config/locales/crowdin/az.yml @@ -1163,6 +1163,9 @@ az: dependencies: "Dependencies" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Display until" attachment: @@ -1537,6 +1540,7 @@ az: not_available: "is not available due to a system configuration." not_deletable: "cannot be deleted." not_current_user: "is not the current user." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "is not a valid date." not_a_datetime: "is not a valid date time." @@ -2098,6 +2102,7 @@ az: role: "Role" roles: "Roles" search: "Search" + sprint: "Sprint" start_date: "Start date" status: "Status" state: "State" diff --git a/config/locales/crowdin/be.yml b/config/locales/crowdin/be.yml index 85b6808bd84..5275aa233e0 100644 --- a/config/locales/crowdin/be.yml +++ b/config/locales/crowdin/be.yml @@ -1181,6 +1181,9 @@ be: dependencies: "Dependencies" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Display until" attachment: @@ -1555,6 +1558,7 @@ be: not_available: "is not available due to a system configuration." not_deletable: "cannot be deleted." not_current_user: "is not the current user." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "is not a valid date." not_a_datetime: "is not a valid date time." @@ -2154,6 +2158,7 @@ be: role: "Role" roles: "Roles" search: "Search" + sprint: "Sprint" start_date: "Start date" status: "Status" state: "State" diff --git a/config/locales/crowdin/bg.yml b/config/locales/crowdin/bg.yml index 3eb158e5a2f..b0a521c3e3b 100644 --- a/config/locales/crowdin/bg.yml +++ b/config/locales/crowdin/bg.yml @@ -1163,6 +1163,9 @@ bg: dependencies: "Зависимости" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Показване до" attachment: @@ -1537,6 +1540,7 @@ bg: not_available: "не е наличен поради системна конфигурация." not_deletable: "не може да бъде изтрито." not_current_user: "не е текущият потребител." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "е невалидна дата" not_a_datetime: "не е валидна дата и час." @@ -2098,6 +2102,7 @@ bg: role: "Роля" roles: "Роли" search: "Търсене" + sprint: "Sprint" start_date: "Начална дата" status: "Състояние" state: "State" diff --git a/config/locales/crowdin/ca.yml b/config/locales/crowdin/ca.yml index 413512873f2..a7b88070a45 100644 --- a/config/locales/crowdin/ca.yml +++ b/config/locales/crowdin/ca.yml @@ -1160,6 +1160,9 @@ ca: dependencies: "Dependències" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Mostrar fins" attachment: @@ -1534,6 +1537,7 @@ ca: not_available: "no és disponible degut a la configuració del sistema." not_deletable: "no es pot eliminar." not_current_user: "no és l'usuari actual." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "no és una data vàlida." not_a_datetime: "no és una data-i-hora vàlida." @@ -2095,6 +2099,7 @@ ca: role: "Rol" roles: "Rols" search: "Cercar" + sprint: "Sprint" start_date: "Data d'inici" status: "Estat" state: "State" diff --git a/config/locales/crowdin/ckb-IR.yml b/config/locales/crowdin/ckb-IR.yml index 9c1ff521356..12d66461d9b 100644 --- a/config/locales/crowdin/ckb-IR.yml +++ b/config/locales/crowdin/ckb-IR.yml @@ -1163,6 +1163,9 @@ ckb-IR: dependencies: "Dependencies" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Display until" attachment: @@ -1537,6 +1540,7 @@ ckb-IR: not_available: "is not available due to a system configuration." not_deletable: "cannot be deleted." not_current_user: "is not the current user." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "is not a valid date." not_a_datetime: "is not a valid date time." @@ -2098,6 +2102,7 @@ ckb-IR: role: "Role" roles: "Roles" search: "Search" + sprint: "Sprint" start_date: "Start date" status: "Status" state: "State" diff --git a/config/locales/crowdin/cs.yml b/config/locales/crowdin/cs.yml index 316acff1f21..0ba86696b55 100644 --- a/config/locales/crowdin/cs.yml +++ b/config/locales/crowdin/cs.yml @@ -1181,6 +1181,9 @@ cs: dependencies: "Závislosti" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Zobrazit do" attachment: @@ -1555,6 +1558,7 @@ cs: not_available: "není k dispozici kvůli konfiguraci systému." not_deletable: "nelze odstranit" not_current_user: "není aktuální uživatel." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "nenalezeno." not_a_date: "není platné datum." not_a_datetime: "není platný čas." @@ -2154,6 +2158,7 @@ cs: role: "Role" roles: "Role" search: "Vyhledávání" + sprint: "Sprint" start_date: "Datum zahájení" status: "Stav" state: "Stav" diff --git a/config/locales/crowdin/da.yml b/config/locales/crowdin/da.yml index bdb5fbeba92..52eae6cb415 100644 --- a/config/locales/crowdin/da.yml +++ b/config/locales/crowdin/da.yml @@ -1161,6 +1161,9 @@ da: dependencies: "Aflæggere" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Display until" attachment: @@ -1535,6 +1538,7 @@ da: not_available: "is not available due to a system configuration." not_deletable: "Kan ikke slettes" not_current_user: "er ikke den aktuelle bruger." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "is not a valid date." not_a_datetime: "is not a valid date time." @@ -2096,6 +2100,7 @@ da: role: "Rolle" roles: "Rollee" search: "Søg" + sprint: "Sprint" start_date: "Start dato" status: "Status" state: "State" diff --git a/config/locales/crowdin/de.yml b/config/locales/crowdin/de.yml index 220842a45be..97dfd27a31d 100644 --- a/config/locales/crowdin/de.yml +++ b/config/locales/crowdin/de.yml @@ -1155,6 +1155,9 @@ de: dependencies: "Abhängigkeiten" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Anzeigen bis" attachment: @@ -1529,6 +1532,7 @@ de: not_available: "ist aufgrund einer Systemkonfiguration nicht verfügbar." not_deletable: "kann nicht entfernt werden." not_current_user: "ist nicht der aktuelle Benutzer." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "nicht gefunden." not_a_date: "ist kein gültiges Datum." not_a_datetime: "ist kein gültiges Datum." @@ -2090,6 +2094,7 @@ de: role: "Rollen" roles: "Rollen" search: "Suche" + sprint: "Sprint" start_date: "Anfangstermin" status: "Status" state: "Status" diff --git a/config/locales/crowdin/el.yml b/config/locales/crowdin/el.yml index 161a6f7d832..599f5c171d0 100644 --- a/config/locales/crowdin/el.yml +++ b/config/locales/crowdin/el.yml @@ -1159,6 +1159,9 @@ el: dependencies: "Εξαρτήσεις" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Εμφάνιση μέχρι" attachment: @@ -1533,6 +1536,7 @@ el: not_available: "is not available due to a system configuration." not_deletable: "δεν μπορεί να διαγραφεί." not_current_user: "δεν είναι ο τρέχων χρήστης." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "δεν είναι έγκυρη ημερομηνία." not_a_datetime: "δεν είναι έγκυρη ημερομηνία και ώρα." @@ -2094,6 +2098,7 @@ el: role: "Ρόλος" roles: "Ρόλοι" search: "Αναζήτηση" + sprint: "Sprint" start_date: "Ημερομηνία έναρξης" status: "Κατάσταση" state: "State" diff --git a/config/locales/crowdin/eo.yml b/config/locales/crowdin/eo.yml index 2c3dc96fc15..cf846739ac0 100644 --- a/config/locales/crowdin/eo.yml +++ b/config/locales/crowdin/eo.yml @@ -1163,6 +1163,9 @@ eo: dependencies: "Dependencies" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Montri ĝis" attachment: @@ -1537,6 +1540,7 @@ eo: not_available: "is not available due to a system configuration." not_deletable: "cannot be deleted." not_current_user: "ne estas la nuna uzanto." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "Ĝi ne estas valida dato." not_a_datetime: "Ĝi ne estas valida dato/horo." @@ -2098,6 +2102,7 @@ eo: role: "Rolo" roles: "Roloj" search: "Search" + sprint: "Sprint" start_date: "Komencdato" status: "Stato" state: "State" diff --git a/config/locales/crowdin/es.yml b/config/locales/crowdin/es.yml index 6f9985b9565..d2abdd8bd33 100644 --- a/config/locales/crowdin/es.yml +++ b/config/locales/crowdin/es.yml @@ -1160,6 +1160,9 @@ es: dependencies: "Dependencias" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Mostrar hasta" attachment: @@ -1534,6 +1537,7 @@ es: not_available: "no está disponible debido a una configuración del sistema." not_deletable: "no se puede eliminar." not_current_user: "no es el usuario actual." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "no encontrado." not_a_date: "no es una fecha válida." not_a_datetime: "no es una fecha/hora válida." @@ -2095,6 +2099,7 @@ es: role: "Perfil" roles: "Roles" search: "Buscar" + sprint: "Sprint" start_date: "Fecha de inicio" status: "Estado" state: "Estado" diff --git a/config/locales/crowdin/et.yml b/config/locales/crowdin/et.yml index 96482597289..75a95df714b 100644 --- a/config/locales/crowdin/et.yml +++ b/config/locales/crowdin/et.yml @@ -1163,6 +1163,9 @@ et: dependencies: "Sõltuvused" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Display until" attachment: @@ -1537,6 +1540,7 @@ et: not_available: "is not available due to a system configuration." not_deletable: "cannot be deleted." not_current_user: "is not the current user." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "ei leitud." not_a_date: "pole korrektne kuupäev." not_a_datetime: "is not a valid date time." @@ -2098,6 +2102,7 @@ et: role: "Roll" roles: "Rollid" search: "Otsi" + sprint: "Sprint" start_date: "Alguskuupäev" status: "Olek" state: "State" diff --git a/config/locales/crowdin/eu.yml b/config/locales/crowdin/eu.yml index 333c08b53fa..e0c8940b1cc 100644 --- a/config/locales/crowdin/eu.yml +++ b/config/locales/crowdin/eu.yml @@ -1163,6 +1163,9 @@ eu: dependencies: "Dependencies" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Display until" attachment: @@ -1537,6 +1540,7 @@ eu: not_available: "is not available due to a system configuration." not_deletable: "cannot be deleted." not_current_user: "is not the current user." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "is not a valid date." not_a_datetime: "is not a valid date time." @@ -2098,6 +2102,7 @@ eu: role: "Role" roles: "Roles" search: "Search" + sprint: "Sprint" start_date: "Hasiera data" status: "Status" state: "State" diff --git a/config/locales/crowdin/fa.yml b/config/locales/crowdin/fa.yml index 80c4789fa27..9e268c881f7 100644 --- a/config/locales/crowdin/fa.yml +++ b/config/locales/crowdin/fa.yml @@ -1163,6 +1163,9 @@ fa: dependencies: "Dependencies" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Display until" attachment: @@ -1537,6 +1540,7 @@ fa: not_available: "is not available due to a system configuration." not_deletable: "cannot be deleted." not_current_user: "is not the current user." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "is not a valid date." not_a_datetime: "is not a valid date time." @@ -2098,6 +2102,7 @@ fa: role: "Role" roles: "نقش‌ها" search: "Search" + sprint: "Sprint" start_date: "تاریخ شروع" status: "وضعیت" state: "State" diff --git a/config/locales/crowdin/fi.yml b/config/locales/crowdin/fi.yml index e8907dab77d..4ba7336bd1b 100644 --- a/config/locales/crowdin/fi.yml +++ b/config/locales/crowdin/fi.yml @@ -1163,6 +1163,9 @@ fi: dependencies: "Riippuvuudet" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Näytä tähän päivään asti" attachment: @@ -1537,6 +1540,7 @@ fi: not_available: "is not available due to a system configuration." not_deletable: "cannot be deleted." not_current_user: "is not the current user." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "ei ole kelvollinen päivämäärä." not_a_datetime: "ei ole kelvollinen aika." @@ -2098,6 +2102,7 @@ fi: role: "Rooli" roles: "Roolit" search: "Haku" + sprint: "Sprint" start_date: "Aloituspäivä" status: "Tila" state: "State" diff --git a/config/locales/crowdin/fil.yml b/config/locales/crowdin/fil.yml index b644553bbad..bc2071f4e92 100644 --- a/config/locales/crowdin/fil.yml +++ b/config/locales/crowdin/fil.yml @@ -1163,6 +1163,9 @@ fil: dependencies: "Dependencia" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "I-displey hanggang" attachment: @@ -1537,6 +1540,7 @@ fil: not_available: "is not available due to a system configuration." not_deletable: "cannot be deleted." not_current_user: "is not the current user." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "ay hindi balido ang petsa." not_a_datetime: "ay hindi balido ang petsa ng oras." @@ -2098,6 +2102,7 @@ fil: role: "Tungkulin" roles: "Ang mga tungkulin" search: "Hanapin" + sprint: "Sprint" start_date: "Petsa ng pagsimula" status: "Estado" state: "State" diff --git a/config/locales/crowdin/fr.yml b/config/locales/crowdin/fr.yml index 85eaffcdee9..86d2f8c4353 100644 --- a/config/locales/crowdin/fr.yml +++ b/config/locales/crowdin/fr.yml @@ -1161,6 +1161,9 @@ fr: dependencies: "Dépendances" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Afficher jusqu'à" attachment: @@ -1535,6 +1538,7 @@ fr: not_available: "n'est pas disponible en raison d'une configuration système." not_deletable: "ne peut pas être supprimé" not_current_user: "n'est pas l'utilisateur actuel." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "introuvable." not_a_date: "n'est pas une date valide." not_a_datetime: "n'est pas une heure valide." @@ -2096,6 +2100,7 @@ fr: role: "Rôle" roles: "Rôles" search: "Recherche" + sprint: "Sprint" start_date: "Date de début" status: "Statut" state: "État" diff --git a/config/locales/crowdin/he.yml b/config/locales/crowdin/he.yml index 2956b812a80..4934ceb0501 100644 --- a/config/locales/crowdin/he.yml +++ b/config/locales/crowdin/he.yml @@ -1181,6 +1181,9 @@ he: dependencies: "Dependencies" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Display until" attachment: @@ -1555,6 +1558,7 @@ he: not_available: "is not available due to a system configuration." not_deletable: "cannot be deleted." not_current_user: "זה לא המשתמש הנכון." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "is not a valid date." not_a_datetime: "is not a valid date time." @@ -2154,6 +2158,7 @@ he: role: "תפקיד" roles: "תפקידים" search: "חיפוש" + sprint: "Sprint" start_date: "תאריך התחלה" status: "מצב" state: "State" diff --git a/config/locales/crowdin/hi.yml b/config/locales/crowdin/hi.yml index e8e556914be..1b2fcd87e1d 100644 --- a/config/locales/crowdin/hi.yml +++ b/config/locales/crowdin/hi.yml @@ -1161,6 +1161,9 @@ hi: dependencies: "Dependencies" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Display until" attachment: @@ -1535,6 +1538,7 @@ hi: not_available: "is not available due to a system configuration." not_deletable: "cannot be deleted." not_current_user: "is not the current user." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "is not a valid date." not_a_datetime: "is not a valid date time." @@ -2096,6 +2100,7 @@ hi: role: "भूमिका" roles: "भूमिकाएं" search: "Search" + sprint: "Sprint" start_date: "प्रारंभ दिनांक" status: "अवस्था" state: "State" diff --git a/config/locales/crowdin/hr.yml b/config/locales/crowdin/hr.yml index e5e47bb0845..0d7a0a87dc3 100644 --- a/config/locales/crowdin/hr.yml +++ b/config/locales/crowdin/hr.yml @@ -1172,6 +1172,9 @@ hr: dependencies: "Ovisnosti" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Prikaži do" attachment: @@ -1546,6 +1549,7 @@ hr: not_available: "is not available due to a system configuration." not_deletable: "cannot be deleted." not_current_user: "is not the current user." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "is not a valid date." not_a_datetime: "is not a valid date time." @@ -2126,6 +2130,7 @@ hr: role: "Role" roles: "Role" search: "Pretraživanje" + sprint: "Sprint" start_date: "Datum Početka" status: "Status" state: "State" diff --git a/config/locales/crowdin/hu.yml b/config/locales/crowdin/hu.yml index 6d0a0d72947..e358ae7c5d5 100644 --- a/config/locales/crowdin/hu.yml +++ b/config/locales/crowdin/hu.yml @@ -1162,6 +1162,9 @@ hu: dependencies: "Szükséges összetevők" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Megjelenít eddig" attachment: @@ -1536,6 +1539,7 @@ hu: not_available: "nem érhető el a rendszer konfigurációja miatt.\n" not_deletable: "nem törölhető" not_current_user: "nem az aktuális felhasználó" + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "nem érvényes dátum." not_a_datetime: "ez nem érvényes dátum." @@ -2097,6 +2101,7 @@ hu: role: "Szerepkör" roles: "Szerepkörök" search: "Keresés" + sprint: "Sprint" start_date: "Indulási dátum" status: "Állapot" state: "State" diff --git a/config/locales/crowdin/id.yml b/config/locales/crowdin/id.yml index dfd49eab01a..1936d3d9fce 100644 --- a/config/locales/crowdin/id.yml +++ b/config/locales/crowdin/id.yml @@ -1150,6 +1150,9 @@ id: dependencies: "Dependensi" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Display until" attachment: @@ -1524,6 +1527,7 @@ id: not_available: "tidak tersedia karena konfigurasi sistem." not_deletable: "tidak dapat dihapus." not_current_user: "bukan pengguna saat ini." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "bukan tanggal yang valid." not_a_datetime: "bukan tanggal waktu yang valid." @@ -2066,6 +2070,7 @@ id: role: "Role" roles: "Roles" search: "Cari" + sprint: "Sprint" start_date: "Tanggal start" status: "Status" state: "State" diff --git a/config/locales/crowdin/it.yml b/config/locales/crowdin/it.yml index 71e6b851031..fa1104aef1d 100644 --- a/config/locales/crowdin/it.yml +++ b/config/locales/crowdin/it.yml @@ -1160,6 +1160,9 @@ it: dependencies: "Dipendenze" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Visualizza fino a" attachment: @@ -1534,6 +1537,7 @@ it: not_available: "non è disponibile a causa di una configurazione di sistema." not_deletable: "non può essere eliminato." not_current_user: "non è l'utente attuale." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "non trovato." not_a_date: "non è una data valida." not_a_datetime: "non è un'orario valido." @@ -2095,6 +2099,7 @@ it: role: "Ruolo" roles: "Ruoli" search: "Cerca" + sprint: "Sprint" start_date: "Data di inizio" status: "Stato" state: "Stato" diff --git a/config/locales/crowdin/ja.yml b/config/locales/crowdin/ja.yml index f0170ecf91f..dfb3b9dadd5 100644 --- a/config/locales/crowdin/ja.yml +++ b/config/locales/crowdin/ja.yml @@ -1153,6 +1153,9 @@ ja: dependencies: "依存関係" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "までを表示" attachment: @@ -1527,6 +1530,7 @@ ja: not_available: "はシステム構成のため使用できません。" not_deletable: "削除できません。" not_current_user: "現在のユーザーではありません。" + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "見つかりません。" not_a_date: "は有効な日付ではありません。" not_a_datetime: "は有効な日時ではありません。" @@ -2069,6 +2073,7 @@ ja: role: "ロール" roles: "ロール" search: "検索" + sprint: "Sprint" start_date: "開始日" status: "ステータス" state: "状態" diff --git a/config/locales/crowdin/ka.yml b/config/locales/crowdin/ka.yml index e86516c1e8f..b485914de81 100644 --- a/config/locales/crowdin/ka.yml +++ b/config/locales/crowdin/ka.yml @@ -1163,6 +1163,9 @@ ka: dependencies: "Dependencies" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "ჩვენება სადამდე" attachment: @@ -1537,6 +1540,7 @@ ka: not_available: "is not available due to a system configuration." not_deletable: "cannot be deleted." not_current_user: "is not the current user." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "is not a valid date." not_a_datetime: "is not a valid date time." @@ -2098,6 +2102,7 @@ ka: role: "როლი" roles: "როლები" search: "ძებნა" + sprint: "Sprint" start_date: "დაწყების თარიღი" status: "სტატუსი" state: "State" diff --git a/config/locales/crowdin/kk.yml b/config/locales/crowdin/kk.yml index e4b919b28ac..27b12e6e65f 100644 --- a/config/locales/crowdin/kk.yml +++ b/config/locales/crowdin/kk.yml @@ -1163,6 +1163,9 @@ kk: dependencies: "Dependencies" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Display until" attachment: @@ -1537,6 +1540,7 @@ kk: not_available: "is not available due to a system configuration." not_deletable: "cannot be deleted." not_current_user: "is not the current user." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "is not a valid date." not_a_datetime: "is not a valid date time." @@ -2098,6 +2102,7 @@ kk: role: "Role" roles: "Roles" search: "Search" + sprint: "Sprint" start_date: "Start date" status: "Status" state: "State" diff --git a/config/locales/crowdin/ko.yml b/config/locales/crowdin/ko.yml index 21c41e70c7b..638a26e7480 100644 --- a/config/locales/crowdin/ko.yml +++ b/config/locales/crowdin/ko.yml @@ -1154,6 +1154,9 @@ ko: dependencies: "종속성" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "표시 기한" attachment: @@ -1528,6 +1531,7 @@ ko: not_available: "- 시스템 구성으로 인해 사용 가능하지 않습니다." not_deletable: "- 삭제할 수 없습니다." not_current_user: "은(는) 현재 유효한 사용자가 아닙니다." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "- 찾을 수 없습니다." not_a_date: "은(는) 유효한 날짜가 아닙니다." not_a_datetime: "은(는) 유효한 날짜가 아닙니다." @@ -2070,6 +2074,7 @@ ko: role: "역할" roles: "역할" search: "검색" + sprint: "Sprint" start_date: "시작 날짜" status: "상태" state: "상태" diff --git a/config/locales/crowdin/lt.yml b/config/locales/crowdin/lt.yml index 813157f30b6..aa5a87de83f 100644 --- a/config/locales/crowdin/lt.yml +++ b/config/locales/crowdin/lt.yml @@ -1178,6 +1178,9 @@ lt: dependencies: "Priklausomybės" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Rodyti iki" attachment: @@ -1552,6 +1555,7 @@ lt: not_available: "yra nepasiekiamas dėl sistemos konfigūracijos" not_deletable: "negali būti pašalintas." not_current_user: "nėra dabartinis naudotojas" + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "nėra tinkama data." not_a_datetime: "nėra tinkama data ir laikas." @@ -2151,6 +2155,7 @@ lt: role: "Vaidmuo" roles: "Vaidmenys" search: "Paieška" + sprint: "Sprint" start_date: "Pradžios data" status: "Būsena" state: "State" diff --git a/config/locales/crowdin/lv.yml b/config/locales/crowdin/lv.yml index ecfb28c4a22..42d4ad231c2 100644 --- a/config/locales/crowdin/lv.yml +++ b/config/locales/crowdin/lv.yml @@ -1172,6 +1172,9 @@ lv: dependencies: "Saistītie projekti" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Display until" attachment: @@ -1546,6 +1549,7 @@ lv: not_available: "is not available due to a system configuration." not_deletable: "cannot be deleted." not_current_user: "is not the current user." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "is not a valid date." not_a_datetime: "is not a valid date time." @@ -2126,6 +2130,7 @@ lv: role: "Loma" roles: "Lomas" search: "Meklēšana" + sprint: "Sprint" start_date: "Sākuma datums" status: "Statuss" state: "State" diff --git a/config/locales/crowdin/mn.yml b/config/locales/crowdin/mn.yml index 6688a0803ca..a2759ab42a5 100644 --- a/config/locales/crowdin/mn.yml +++ b/config/locales/crowdin/mn.yml @@ -1163,6 +1163,9 @@ mn: dependencies: "Dependencies" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Display until" attachment: @@ -1537,6 +1540,7 @@ mn: not_available: "is not available due to a system configuration." not_deletable: "cannot be deleted." not_current_user: "is not the current user." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "is not a valid date." not_a_datetime: "is not a valid date time." @@ -2098,6 +2102,7 @@ mn: role: "Role" roles: "Roles" search: "Search" + sprint: "Sprint" start_date: "Start date" status: "Төлөв" state: "State" diff --git a/config/locales/crowdin/ms.yml b/config/locales/crowdin/ms.yml index 7514733d90d..8675e77b1e2 100644 --- a/config/locales/crowdin/ms.yml +++ b/config/locales/crowdin/ms.yml @@ -1152,6 +1152,9 @@ ms: dependencies: "Dependencies" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Paparkan sehingga" attachment: @@ -1526,6 +1529,7 @@ ms: not_available: "adalah tidak tersedia kerana konfigurasi sistem." not_deletable: "tidak dapat dipadamkan." not_current_user: "adalah bukan pengguna semasa." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "tidak dijumpai." not_a_date: "bukan tarikh yang sah." not_a_datetime: "bukan tarikh masa yang sah." @@ -2068,6 +2072,7 @@ ms: role: "Peranan" roles: "Peranan\n" search: "Cari" + sprint: "Sprint" start_date: "Tarikh mula" status: "Status" state: "State" diff --git a/config/locales/crowdin/ne.yml b/config/locales/crowdin/ne.yml index c0e73a38396..f26b67caf71 100644 --- a/config/locales/crowdin/ne.yml +++ b/config/locales/crowdin/ne.yml @@ -1163,6 +1163,9 @@ ne: dependencies: "Dependencies" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Display until" attachment: @@ -1537,6 +1540,7 @@ ne: not_available: "is not available due to a system configuration." not_deletable: "cannot be deleted." not_current_user: "is not the current user." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "is not a valid date." not_a_datetime: "is not a valid date time." @@ -2098,6 +2102,7 @@ ne: role: "Role" roles: "भूमिकाहरु" search: "Search" + sprint: "Sprint" start_date: "Start date" status: "Status" state: "State" diff --git a/config/locales/crowdin/nl.yml b/config/locales/crowdin/nl.yml index 8c6d44ffaa8..7c912c974e3 100644 --- a/config/locales/crowdin/nl.yml +++ b/config/locales/crowdin/nl.yml @@ -1159,6 +1159,9 @@ nl: dependencies: "Afhankelijkheden" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Toon tot" attachment: @@ -1533,6 +1536,7 @@ nl: not_available: "is niet beschikbaar vanwege een systeemconfiguratie." not_deletable: "kan niet worden verwijderd." not_current_user: "is niet de huidige gebruiker." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "niet gevonden." not_a_date: "is geen geldige datum." not_a_datetime: "is geen geldige datum tijd." @@ -2094,6 +2098,7 @@ nl: role: "Rol" roles: "Rollen" search: "Zoeken" + sprint: "Sprint" start_date: "Startdatum" status: "Status" state: "State" diff --git a/config/locales/crowdin/no.yml b/config/locales/crowdin/no.yml index b532cbb52da..30636f8c55c 100644 --- a/config/locales/crowdin/no.yml +++ b/config/locales/crowdin/no.yml @@ -1162,6 +1162,9 @@ dependencies: "Avhengigheter" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Vise til" attachment: @@ -1536,6 +1539,7 @@ not_available: "er ikke tilgjengelig på grunn av en systemkonfigurasjon." not_deletable: "kan ikke slettes." not_current_user: "er ikke gjeldende bruker." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "er ikke en gyldig dato." not_a_datetime: "er ikke et gyldig tidspunkt for datoen." @@ -2097,6 +2101,7 @@ role: "Rolle" roles: "Rolle" search: "Søk" + sprint: "Sprint" start_date: "Startdato" status: "Status" state: "State" diff --git a/config/locales/crowdin/pl.yml b/config/locales/crowdin/pl.yml index 5b7cfe43c69..ebe94ed4c95 100644 --- a/config/locales/crowdin/pl.yml +++ b/config/locales/crowdin/pl.yml @@ -1177,6 +1177,9 @@ pl: dependencies: "Zależności" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Wyświetlaj do" attachment: @@ -1551,6 +1554,7 @@ pl: not_available: "jest niedostępne z powodu konfiguracji systemu." not_deletable: "— nie można usunąć." not_current_user: "nie jest bieżącym użytkownikiem." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "nie znaleziono." not_a_date: "nie jest poprawną datą." not_a_datetime: "nie jest poprawną datą i czasem." @@ -2150,6 +2154,7 @@ pl: role: "Rola" roles: "Role" search: "Wyszukaj" + sprint: "Sprint" start_date: "Data rozpoczęcia" status: "Status" state: "Stan" diff --git a/config/locales/crowdin/pt-BR.yml b/config/locales/crowdin/pt-BR.yml index 52436eff6aa..341d806f82e 100644 --- a/config/locales/crowdin/pt-BR.yml +++ b/config/locales/crowdin/pt-BR.yml @@ -1160,6 +1160,9 @@ pt-BR: dependencies: "Dependências" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Exibir até" attachment: @@ -1534,6 +1537,7 @@ pt-BR: not_available: "não está disponível devido a uma configuração do sistema." not_deletable: "não pode ser excluído." not_current_user: "não é o usuário atual." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "não encontrado." not_a_date: "não é uma data válida." not_a_datetime: "não é uma data/hora válida." @@ -2095,6 +2099,7 @@ pt-BR: role: "Função" roles: "Papéis" search: "Pesquisar" + sprint: "Sprint" start_date: "Data de início" status: "Status" state: "Estado" diff --git a/config/locales/crowdin/pt-PT.yml b/config/locales/crowdin/pt-PT.yml index 2394095a1e4..b301c268bf9 100644 --- a/config/locales/crowdin/pt-PT.yml +++ b/config/locales/crowdin/pt-PT.yml @@ -1160,6 +1160,9 @@ pt-PT: dependencies: "Dependências" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Exibir até" attachment: @@ -1534,6 +1537,7 @@ pt-PT: not_available: "não está disponível devido a uma configuração do sistema." not_deletable: "não pode ser eliminado" not_current_user: "não é o utilizador atual." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "não encontrado." not_a_date: "não é uma data válida." not_a_datetime: "não é uma data/hora válida." @@ -2095,6 +2099,7 @@ pt-PT: role: "Função" roles: "Papel" search: "Pesquisar" + sprint: "Sprint" start_date: "Data de início" status: "Situação" state: "Estado" diff --git a/config/locales/crowdin/ro.yml b/config/locales/crowdin/ro.yml index ee62092956e..66ad70a6d1c 100644 --- a/config/locales/crowdin/ro.yml +++ b/config/locales/crowdin/ro.yml @@ -1172,6 +1172,9 @@ ro: dependencies: "Dependenţe" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Afişare până la" attachment: @@ -1546,6 +1549,7 @@ ro: not_available: "nu este disponibil din cauza unei configurații a sistemului." not_deletable: "%s nu poate fi șters." not_current_user: "nu este utilizatorul curent." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "nu a fost găsit." not_a_date: "Acest câmp trebuie să conțină o dată validă." not_a_datetime: "nu este o dată-ora validă." @@ -2126,6 +2130,7 @@ ro: role: "Rol" roles: "Roluri" search: "Caută" + sprint: "Sprint" start_date: "Dată început" status: "Stare" state: "State" diff --git a/config/locales/crowdin/ru.yml b/config/locales/crowdin/ru.yml index f4697639978..021bca0a901 100644 --- a/config/locales/crowdin/ru.yml +++ b/config/locales/crowdin/ru.yml @@ -1179,6 +1179,9 @@ ru: dependencies: "Связи" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Отобразить до" attachment: @@ -1553,6 +1556,7 @@ ru: not_available: "недоступно из-за конфигурации системы." not_deletable: "не может быть удален." not_current_user: "не является текущим пользователем." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "не найдено." not_a_date: "не является допустимой датой." not_a_datetime: "дата и время не являются допустимыми." @@ -2152,6 +2156,7 @@ ru: role: "Роль" roles: "Роли" search: "Поиск" + sprint: "Sprint" start_date: "Дата начала" status: "Статус" state: "Область" diff --git a/config/locales/crowdin/rw.yml b/config/locales/crowdin/rw.yml index aca55c27b3b..a60cd8cc473 100644 --- a/config/locales/crowdin/rw.yml +++ b/config/locales/crowdin/rw.yml @@ -1163,6 +1163,9 @@ rw: dependencies: "Dependencies" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Display until" attachment: @@ -1537,6 +1540,7 @@ rw: not_available: "is not available due to a system configuration." not_deletable: "cannot be deleted." not_current_user: "is not the current user." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "is not a valid date." not_a_datetime: "is not a valid date time." @@ -2098,6 +2102,7 @@ rw: role: "Role" roles: "Roles" search: "Search" + sprint: "Sprint" start_date: "Start date" status: "Status" state: "State" diff --git a/config/locales/crowdin/si.yml b/config/locales/crowdin/si.yml index ef912805158..47967f8a039 100644 --- a/config/locales/crowdin/si.yml +++ b/config/locales/crowdin/si.yml @@ -1163,6 +1163,9 @@ si: dependencies: "පරායත්තතා" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "ප්රදර්ශනය වන තුරු" attachment: @@ -1537,6 +1540,7 @@ si: not_available: "is not available due to a system configuration." not_deletable: "cannot be deleted." not_current_user: "is not the current user." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "වලංගු දිනයක් නොවේ." not_a_datetime: "වලංගු දිනය කාලය නොවේ." @@ -2098,6 +2102,7 @@ si: role: "කාර්යභාරය" roles: "භූමිකාවන්" search: "සොයන්න" + sprint: "Sprint" start_date: "ආරම්භක දිනය" status: "තත්වය" state: "State" diff --git a/config/locales/crowdin/sk.yml b/config/locales/crowdin/sk.yml index aa07bbdff3d..1fdd0d87d0a 100644 --- a/config/locales/crowdin/sk.yml +++ b/config/locales/crowdin/sk.yml @@ -1181,6 +1181,9 @@ sk: dependencies: "Závislosti" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Zobrazovať, až kým" attachment: @@ -1555,6 +1558,7 @@ sk: not_available: "is not available due to a system configuration." not_deletable: "cannot be deleted." not_current_user: "is not the current user." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "nie je platný dátum." not_a_datetime: "nie je platný dátum a čas." @@ -2154,6 +2158,7 @@ sk: role: "Rola" roles: "Roly" search: "Vyhľadávanie" + sprint: "Sprint" start_date: "Dátum začiatku" status: "Stav" state: "State" diff --git a/config/locales/crowdin/sl.yml b/config/locales/crowdin/sl.yml index 7f38fecdf9d..c1d9f712d15 100644 --- a/config/locales/crowdin/sl.yml +++ b/config/locales/crowdin/sl.yml @@ -1180,6 +1180,9 @@ sl: dependencies: "Odvisnosti" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Prikaži do" attachment: @@ -1554,6 +1557,7 @@ sl: not_available: "is not available due to a system configuration." not_deletable: "se ne da izbrisati." not_current_user: "ni trenutni uporabnik." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "ni veljaven datum" not_a_datetime: "ni veljaven datum." @@ -2153,6 +2157,7 @@ sl: role: "Vloga" roles: "Vloge" search: "Išči" + sprint: "Sprint" start_date: "Datum začetka" status: "Stanje" state: "State" diff --git a/config/locales/crowdin/sr.yml b/config/locales/crowdin/sr.yml index b91b0813d3c..82cf3bb349c 100644 --- a/config/locales/crowdin/sr.yml +++ b/config/locales/crowdin/sr.yml @@ -1172,6 +1172,9 @@ sr: dependencies: "Dependencies" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Display until" attachment: @@ -1546,6 +1549,7 @@ sr: not_available: "is not available due to a system configuration." not_deletable: "cannot be deleted." not_current_user: "is not the current user." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "is not a valid date." not_a_datetime: "is not a valid date time." @@ -2126,6 +2130,7 @@ sr: role: "Role" roles: "Roles" search: "Search" + sprint: "Sprint" start_date: "Start date" status: "Status" state: "State" diff --git a/config/locales/crowdin/sv.yml b/config/locales/crowdin/sv.yml index e3a88826094..6be9ad903ac 100644 --- a/config/locales/crowdin/sv.yml +++ b/config/locales/crowdin/sv.yml @@ -1163,6 +1163,9 @@ sv: dependencies: "Beroenden" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Visa fram till" attachment: @@ -1537,6 +1540,7 @@ sv: not_available: "is not available due to a system configuration." not_deletable: "kan inte raderas." not_current_user: "is not the current user." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "hittades inte." not_a_date: "är inte är ett giltigt datum." not_a_datetime: "är inte en giltig datumtid." @@ -2098,6 +2102,7 @@ sv: role: "Roll" roles: "Roll" search: "Sök" + sprint: "Sprint" start_date: "Startdatum" status: "Status" state: "State" diff --git a/config/locales/crowdin/th.yml b/config/locales/crowdin/th.yml index aef916f1251..bbf146e432f 100644 --- a/config/locales/crowdin/th.yml +++ b/config/locales/crowdin/th.yml @@ -1154,6 +1154,9 @@ th: dependencies: "ส่วนที่อ้างอิง" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Display until" attachment: @@ -1528,6 +1531,7 @@ th: not_available: "is not available due to a system configuration." not_deletable: "cannot be deleted." not_current_user: "is not the current user." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "is not a valid date." not_a_datetime: "is not a valid date time." @@ -2070,6 +2074,7 @@ th: role: "บทบาท" roles: "บทบาท" search: "ค้นหา" + sprint: "Sprint" start_date: "วันเริ่มต้น" status: "สถานะ" state: "State" diff --git a/config/locales/crowdin/tr.yml b/config/locales/crowdin/tr.yml index 806c57a3db3..30b15cdb999 100644 --- a/config/locales/crowdin/tr.yml +++ b/config/locales/crowdin/tr.yml @@ -1163,6 +1163,9 @@ tr: dependencies: "Bağımlılıklar" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Son yayın tarihi" attachment: @@ -1537,6 +1540,7 @@ tr: not_available: "Sistem yapılandırması nedeniyle kullanılamaz.\n" not_deletable: "kaldırılamadı." not_current_user: "mevcut kullanıcı değil." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "bulunamadı." not_a_date: "geçerli bir tarih değil." not_a_datetime: "geçerli bir zaman değil." @@ -2098,6 +2102,7 @@ tr: role: "Rol" roles: "Yetkiler" search: "Ara" + sprint: "Sprint" start_date: "Başlangıç tarihi" status: "Durum" state: "Durum" diff --git a/config/locales/crowdin/uk.yml b/config/locales/crowdin/uk.yml index 7d01970a554..0444e2e9e20 100644 --- a/config/locales/crowdin/uk.yml +++ b/config/locales/crowdin/uk.yml @@ -1175,6 +1175,9 @@ uk: dependencies: "Залежності" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Показувати до" attachment: @@ -1549,6 +1552,7 @@ uk: not_available: "– недоступно через налаштування системи." not_deletable: "не можна видалити." not_current_user: "не поточний користувач." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "не знайдено." not_a_date: "не є дійсною датою." not_a_datetime: "не є дійсним датою." @@ -2148,6 +2152,7 @@ uk: role: "роль" roles: "Роль" search: "Пошук" + sprint: "Sprint" start_date: "Початок" status: "Статус" state: "Стан" diff --git a/config/locales/crowdin/uz.yml b/config/locales/crowdin/uz.yml index 81cf93dc2c9..e62769f5f6b 100644 --- a/config/locales/crowdin/uz.yml +++ b/config/locales/crowdin/uz.yml @@ -1163,6 +1163,9 @@ uz: dependencies: "Dependencies" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Display until" attachment: @@ -1537,6 +1540,7 @@ uz: not_available: "is not available due to a system configuration." not_deletable: "cannot be deleted." not_current_user: "is not the current user." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "not found." not_a_date: "is not a valid date." not_a_datetime: "is not a valid date time." @@ -2098,6 +2102,7 @@ uz: role: "Role" roles: "Roles" search: "Search" + sprint: "Sprint" start_date: "Start date" status: "Status" state: "State" diff --git a/config/locales/crowdin/vi.yml b/config/locales/crowdin/vi.yml index 5b2ceb09f5a..ecb8a391886 100644 --- a/config/locales/crowdin/vi.yml +++ b/config/locales/crowdin/vi.yml @@ -1152,6 +1152,9 @@ vi: dependencies: "phụ thuộc" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "Hiển thị cho đến khi" attachment: @@ -1526,6 +1529,7 @@ vi: not_available: "không khả dụng do cấu hình hệ thống." not_deletable: "không thể xóa được." not_current_user: "không phải là người dùng hiện tại." + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "không tìm thấy." not_a_date: "không phải là ngày hợp lệ" not_a_datetime: "không phải là thời gian hợp lệ" @@ -2068,6 +2072,7 @@ vi: role: "Vai trò" roles: "Vai trò" search: "tìm kiếm" + sprint: "Sprint" start_date: "Ngày bắt đầu" status: "Trạng thái" state: "tiểu bang" diff --git a/config/locales/crowdin/zh-CN.yml b/config/locales/crowdin/zh-CN.yml index e7cb50215fd..d956b92104e 100644 --- a/config/locales/crowdin/zh-CN.yml +++ b/config/locales/crowdin/zh-CN.yml @@ -1150,6 +1150,9 @@ zh-CN: dependencies: "依赖项" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "显示截止日期" attachment: @@ -1524,6 +1527,7 @@ zh-CN: not_available: "因系统配置而不可用。" not_deletable: "无法删除。" not_current_user: "不是当前用户。" + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "未找到" not_a_date: "不是有效的日期。" not_a_datetime: "不是有效的日期时间。" @@ -2066,6 +2070,7 @@ zh-CN: role: "角色" roles: "角色" search: "搜索" + sprint: "Sprint" start_date: "开始日期" status: "状态" state: "地区" diff --git a/config/locales/crowdin/zh-TW.yml b/config/locales/crowdin/zh-TW.yml index f13f059aeb8..457f8332d6b 100644 --- a/config/locales/crowdin/zh-TW.yml +++ b/config/locales/crowdin/zh-TW.yml @@ -1150,6 +1150,9 @@ zh-TW: dependencies: "依賴套件" activerecord: attributes: + agile/sprint: + sharing: "Sharing" + finish_date: "End date" announcements: show_until: "只顯示到" attachment: @@ -1524,6 +1527,7 @@ zh-TW: not_available: "由於系統配置所以不可用" not_deletable: "無法刪除" not_current_user: "不是目前使用者。" + only_one_active_sprint_allowed: "only one active sprint is allowed per project." not_found: "未找到" not_a_date: "不是有效的日期。" not_a_datetime: "不是有效的日期時間。" @@ -2066,6 +2070,7 @@ zh-TW: role: "角色" roles: "角色" search: "搜尋" + sprint: "Sprint" start_date: "起始日期" status: "狀態" state: "狀態" diff --git a/modules/backlogs/config/locales/crowdin/af.yml b/modules/backlogs/config/locales/crowdin/af.yml index 8a8df3394e6..20d24018589 100644 --- a/modules/backlogs/config/locales/crowdin/af.yml +++ b/modules/backlogs/config/locales/crowdin/af.yml @@ -120,10 +120,6 @@ af: label_column_in_backlog: "Kolom in agterstand" label_points_burn_down: "Af" label_points_burn_up: "Op" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "Sprint belemmerings" label_task_board: "Taak bord" permission_view_master_backlog: "Kyk na meester-agterstand" diff --git a/modules/backlogs/config/locales/crowdin/ar.yml b/modules/backlogs/config/locales/crowdin/ar.yml index bba9490e754..a00c06b69cd 100644 --- a/modules/backlogs/config/locales/crowdin/ar.yml +++ b/modules/backlogs/config/locales/crowdin/ar.yml @@ -128,10 +128,6 @@ ar: label_column_in_backlog: "عمود في العمل المتراكم غير المنجز" label_points_burn_down: "الأسفل" label_points_burn_up: "الأعلى" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "عوائق السباق" label_task_board: "لوحة المهمة" permission_view_master_backlog: "عرض العمل الرئيسي المتراكم غير المنجز" diff --git a/modules/backlogs/config/locales/crowdin/az.yml b/modules/backlogs/config/locales/crowdin/az.yml index beefe86de19..309b5b19a3f 100644 --- a/modules/backlogs/config/locales/crowdin/az.yml +++ b/modules/backlogs/config/locales/crowdin/az.yml @@ -120,10 +120,6 @@ az: label_column_in_backlog: "Column in backlog" label_points_burn_down: "Down" label_points_burn_up: "Up" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "Sprint Impediments" label_task_board: "Task board" permission_view_master_backlog: "View master backlog" diff --git a/modules/backlogs/config/locales/crowdin/be.yml b/modules/backlogs/config/locales/crowdin/be.yml index 08248f9f22f..12c334f48a8 100644 --- a/modules/backlogs/config/locales/crowdin/be.yml +++ b/modules/backlogs/config/locales/crowdin/be.yml @@ -124,10 +124,6 @@ be: label_column_in_backlog: "Column in backlog" label_points_burn_down: "Down" label_points_burn_up: "Up" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "Sprint Impediments" label_task_board: "Task board" permission_view_master_backlog: "View master backlog" diff --git a/modules/backlogs/config/locales/crowdin/bg.yml b/modules/backlogs/config/locales/crowdin/bg.yml index 74fa2864dbc..e16dce07c86 100644 --- a/modules/backlogs/config/locales/crowdin/bg.yml +++ b/modules/backlogs/config/locales/crowdin/bg.yml @@ -120,10 +120,6 @@ bg: label_column_in_backlog: "Column in backlog" label_points_burn_down: "Надолу" label_points_burn_up: "Нагоре" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "Пречки за спринт" label_task_board: "Task board" permission_view_master_backlog: "View master backlog" diff --git a/modules/backlogs/config/locales/crowdin/ca.yml b/modules/backlogs/config/locales/crowdin/ca.yml index e97a3abc45a..4d52aad8369 100644 --- a/modules/backlogs/config/locales/crowdin/ca.yml +++ b/modules/backlogs/config/locales/crowdin/ca.yml @@ -120,10 +120,6 @@ ca: label_column_in_backlog: "Columna al backlog" label_points_burn_down: "A baix" label_points_burn_up: "Amunt" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "Impediments de sprint" label_task_board: "Tauler de tasques" permission_view_master_backlog: "Visualitza el backlog mestre" diff --git a/modules/backlogs/config/locales/crowdin/ckb-IR.yml b/modules/backlogs/config/locales/crowdin/ckb-IR.yml index c813b5d1c2a..3bc24142a22 100644 --- a/modules/backlogs/config/locales/crowdin/ckb-IR.yml +++ b/modules/backlogs/config/locales/crowdin/ckb-IR.yml @@ -120,10 +120,6 @@ ckb-IR: label_column_in_backlog: "Column in backlog" label_points_burn_down: "Down" label_points_burn_up: "Up" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "Sprint Impediments" label_task_board: "Task board" permission_view_master_backlog: "View master backlog" diff --git a/modules/backlogs/config/locales/crowdin/cs.yml b/modules/backlogs/config/locales/crowdin/cs.yml index 689464be225..f31f03d363c 100644 --- a/modules/backlogs/config/locales/crowdin/cs.yml +++ b/modules/backlogs/config/locales/crowdin/cs.yml @@ -124,10 +124,6 @@ cs: label_column_in_backlog: "Sloupec v nevyřízené pozici" label_points_burn_down: "Dolů" label_points_burn_up: "Nahoru" - label_select_type: "Vyberte typ" - label_select_types: "Vyberte typy" - label_selected_type: "Vybraný typ" - label_selected_types: "Vybrané typy" label_sprint_impediments: "Běh impedimenty" label_task_board: "Tabule úkolů" permission_view_master_backlog: "Zobrazit hlavní nevyřízené položky" diff --git a/modules/backlogs/config/locales/crowdin/da.yml b/modules/backlogs/config/locales/crowdin/da.yml index 5e15d9e3e05..b903386f5d8 100644 --- a/modules/backlogs/config/locales/crowdin/da.yml +++ b/modules/backlogs/config/locales/crowdin/da.yml @@ -120,10 +120,6 @@ da: label_column_in_backlog: "Kolonne i backlog" label_points_burn_down: "Ned" label_points_burn_up: "Op" - label_select_type: "Vælg en type" - label_select_types: "Vælg typer" - label_selected_type: "Valgt type" - label_selected_types: "Valgte typer" label_sprint_impediments: "Sprint-hindringer" label_task_board: "Opgaveoversigt" permission_view_master_backlog: "Se hoved-backlog" diff --git a/modules/backlogs/config/locales/crowdin/de.yml b/modules/backlogs/config/locales/crowdin/de.yml index 77c4d219cfa..7f650a56f84 100644 --- a/modules/backlogs/config/locales/crowdin/de.yml +++ b/modules/backlogs/config/locales/crowdin/de.yml @@ -120,10 +120,6 @@ de: label_column_in_backlog: "Spalte im Backlog" label_points_burn_down: "Runter" label_points_burn_up: "Hoch" - label_select_type: "Typ auswählen" - label_select_types: "Typen auswählen" - label_selected_type: "Ausgewählter Typ" - label_selected_types: "Ausgewähle Typen" label_sprint_impediments: "Sprint Hindernisse" label_task_board: "Taskboard" permission_view_master_backlog: "Master Backlog ansehen" diff --git a/modules/backlogs/config/locales/crowdin/el.yml b/modules/backlogs/config/locales/crowdin/el.yml index ad11cf51a58..53021e93381 100644 --- a/modules/backlogs/config/locales/crowdin/el.yml +++ b/modules/backlogs/config/locales/crowdin/el.yml @@ -120,10 +120,6 @@ el: label_column_in_backlog: "Στήλη στο backlog" label_points_burn_down: "Κάτω" label_points_burn_up: "Πάνω" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "Εμπόδια Sprint" label_task_board: "Πίνακας εργασιών" permission_view_master_backlog: "Εμφάνιση του κύριου backlog" diff --git a/modules/backlogs/config/locales/crowdin/eo.yml b/modules/backlogs/config/locales/crowdin/eo.yml index 9127ed8b4ea..9823ba39aec 100644 --- a/modules/backlogs/config/locales/crowdin/eo.yml +++ b/modules/backlogs/config/locales/crowdin/eo.yml @@ -120,10 +120,6 @@ eo: label_column_in_backlog: "Column in backlog" label_points_burn_down: "Malsupren" label_points_burn_up: "Supren" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "Sprint Impediments" label_task_board: "Task board" permission_view_master_backlog: "View master backlog" diff --git a/modules/backlogs/config/locales/crowdin/es.yml b/modules/backlogs/config/locales/crowdin/es.yml index 6c539d24af0..6c924301122 100644 --- a/modules/backlogs/config/locales/crowdin/es.yml +++ b/modules/backlogs/config/locales/crowdin/es.yml @@ -120,10 +120,6 @@ es: label_column_in_backlog: "Columna en backlog" label_points_burn_down: "Abajo" label_points_burn_up: "Arriba" - label_select_type: "Selecciona un tipo" - label_select_types: "Selecciona tipos" - label_selected_type: "Tipo seleccionado" - label_selected_types: "Tipos seleccionados" label_sprint_impediments: "Impedimentos de sprint" label_task_board: "Tablero de tareas" permission_view_master_backlog: "Ver backlog maestro" diff --git a/modules/backlogs/config/locales/crowdin/et.yml b/modules/backlogs/config/locales/crowdin/et.yml index e69f28c1bc2..70ca6ef2f2f 100644 --- a/modules/backlogs/config/locales/crowdin/et.yml +++ b/modules/backlogs/config/locales/crowdin/et.yml @@ -120,10 +120,6 @@ et: label_column_in_backlog: "Column in backlog" label_points_burn_down: "Alla" label_points_burn_up: "Üles" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "Sprint Impediments" label_task_board: "Task board" permission_view_master_backlog: "View master backlog" diff --git a/modules/backlogs/config/locales/crowdin/eu.yml b/modules/backlogs/config/locales/crowdin/eu.yml index 9b3fb81a5b4..aee74542e1a 100644 --- a/modules/backlogs/config/locales/crowdin/eu.yml +++ b/modules/backlogs/config/locales/crowdin/eu.yml @@ -120,10 +120,6 @@ eu: label_column_in_backlog: "Column in backlog" label_points_burn_down: "Behera" label_points_burn_up: "Gora" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "Sprint Impediments" label_task_board: "Task board" permission_view_master_backlog: "View master backlog" diff --git a/modules/backlogs/config/locales/crowdin/fa.yml b/modules/backlogs/config/locales/crowdin/fa.yml index c32841adeeb..693d253d5a7 100644 --- a/modules/backlogs/config/locales/crowdin/fa.yml +++ b/modules/backlogs/config/locales/crowdin/fa.yml @@ -120,10 +120,6 @@ fa: label_column_in_backlog: "ستون در پس‌افت" label_points_burn_down: "پایین" label_points_burn_up: "بالا" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: " موانع تاخت" label_task_board: "تابلوی وظیفه" permission_view_master_backlog: "نمایش بک لاگ اصلی" diff --git a/modules/backlogs/config/locales/crowdin/fi.yml b/modules/backlogs/config/locales/crowdin/fi.yml index b83f006523e..b2b927ccdc8 100644 --- a/modules/backlogs/config/locales/crowdin/fi.yml +++ b/modules/backlogs/config/locales/crowdin/fi.yml @@ -120,10 +120,6 @@ fi: label_column_in_backlog: "Sarake työjonossa" label_points_burn_down: "Alas" label_points_burn_up: "Ylös" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "Sprintin esteet" label_task_board: "Tehtävätaulu" permission_view_master_backlog: "Näytä pääasiallinen työjono" diff --git a/modules/backlogs/config/locales/crowdin/fil.yml b/modules/backlogs/config/locales/crowdin/fil.yml index 17d12463822..6f0109336b2 100644 --- a/modules/backlogs/config/locales/crowdin/fil.yml +++ b/modules/backlogs/config/locales/crowdin/fil.yml @@ -120,10 +120,6 @@ fil: label_column_in_backlog: "Column in backlog" label_points_burn_down: "Down" label_points_burn_up: "Up" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "Sprint Impediments" label_task_board: "Task board" permission_view_master_backlog: "View master backlog" diff --git a/modules/backlogs/config/locales/crowdin/fr.yml b/modules/backlogs/config/locales/crowdin/fr.yml index 8eaf0406c5b..1f4fbddd1ca 100644 --- a/modules/backlogs/config/locales/crowdin/fr.yml +++ b/modules/backlogs/config/locales/crowdin/fr.yml @@ -120,10 +120,6 @@ fr: label_column_in_backlog: "Colonne dans le backlog" label_points_burn_down: "Vers le bas" label_points_burn_up: "Vers le haut" - label_select_type: "Sélectionnez un type" - label_select_types: "Sélectionnez le type" - label_selected_type: "Type sélectionné" - label_selected_types: "Types sélectionnés" label_sprint_impediments: "Obstacles de sprint" label_task_board: "Tableau des tâches" permission_view_master_backlog: "Afficher le backlog principal" diff --git a/modules/backlogs/config/locales/crowdin/he.yml b/modules/backlogs/config/locales/crowdin/he.yml index 7b8c93d2b69..a3d2729ba67 100644 --- a/modules/backlogs/config/locales/crowdin/he.yml +++ b/modules/backlogs/config/locales/crowdin/he.yml @@ -124,10 +124,6 @@ he: label_column_in_backlog: "Column in backlog" label_points_burn_down: "Down" label_points_burn_up: "Up" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "Sprint Impediments" label_task_board: "Task board" permission_view_master_backlog: "View master backlog" diff --git a/modules/backlogs/config/locales/crowdin/hi.yml b/modules/backlogs/config/locales/crowdin/hi.yml index 70166309007..c1624aaae8f 100644 --- a/modules/backlogs/config/locales/crowdin/hi.yml +++ b/modules/backlogs/config/locales/crowdin/hi.yml @@ -120,10 +120,6 @@ hi: label_column_in_backlog: "Column in backlog" label_points_burn_down: "Down" label_points_burn_up: "Up" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "Sprint Impediments" label_task_board: "Task board" permission_view_master_backlog: "View master backlog" diff --git a/modules/backlogs/config/locales/crowdin/hr.yml b/modules/backlogs/config/locales/crowdin/hr.yml index c5038cf2468..4a46ad8f6c5 100644 --- a/modules/backlogs/config/locales/crowdin/hr.yml +++ b/modules/backlogs/config/locales/crowdin/hr.yml @@ -122,10 +122,6 @@ hr: label_column_in_backlog: "Stupac u backlogu" label_points_burn_down: "Dolje" label_points_burn_up: "Gore" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "Prepreke perioda razvoja" label_task_board: "Upravitelj zadatcima" permission_view_master_backlog: "Pogledaj glavni backlog" diff --git a/modules/backlogs/config/locales/crowdin/hu.yml b/modules/backlogs/config/locales/crowdin/hu.yml index 9f037ddac77..7aab8818879 100644 --- a/modules/backlogs/config/locales/crowdin/hu.yml +++ b/modules/backlogs/config/locales/crowdin/hu.yml @@ -120,10 +120,6 @@ hu: label_column_in_backlog: "Column in backlog" label_points_burn_down: "Le" label_points_burn_up: "Fel" - label_select_type: "Válasszon típust" - label_select_types: "Típusok kiválasztása" - label_selected_type: "Kiválasztott típus" - label_selected_types: "Kiválasztott típusok" label_sprint_impediments: "Sprint akadályai" label_task_board: "Task board" permission_view_master_backlog: "View master backlog" diff --git a/modules/backlogs/config/locales/crowdin/id.yml b/modules/backlogs/config/locales/crowdin/id.yml index bd19ce678dc..39f4116d3c8 100644 --- a/modules/backlogs/config/locales/crowdin/id.yml +++ b/modules/backlogs/config/locales/crowdin/id.yml @@ -118,10 +118,6 @@ id: label_column_in_backlog: "Kolom di backlog" label_points_burn_down: "Menurun" label_points_burn_up: "Naik" - label_select_type: "Pilih jenis" - label_select_types: "Pilih jenis" - label_selected_type: "Jenis yang dipilih" - label_selected_types: "Jenis yang dipilih" label_sprint_impediments: "Hanbatan kekuatan" label_task_board: "Papan tugas" permission_view_master_backlog: "Lihat backlog master" diff --git a/modules/backlogs/config/locales/crowdin/it.yml b/modules/backlogs/config/locales/crowdin/it.yml index 16f1de330c9..f5040895dfe 100644 --- a/modules/backlogs/config/locales/crowdin/it.yml +++ b/modules/backlogs/config/locales/crowdin/it.yml @@ -120,10 +120,6 @@ it: label_column_in_backlog: "Colonna nel backlog" label_points_burn_down: "Verso il basso" label_points_burn_up: "Verso l'alto" - label_select_type: "Seleziona un tipo" - label_select_types: "Seleziona i tipi" - label_selected_type: "Tipo selezionato" - label_selected_types: "Tipi selezionati" label_sprint_impediments: "Impedimenti allo sprint" label_task_board: "Pannello delle attività" permission_view_master_backlog: "Visualizza il master backlog" diff --git a/modules/backlogs/config/locales/crowdin/ja.yml b/modules/backlogs/config/locales/crowdin/ja.yml index 3ed3aea3394..5af9e30d172 100644 --- a/modules/backlogs/config/locales/crowdin/ja.yml +++ b/modules/backlogs/config/locales/crowdin/ja.yml @@ -118,10 +118,6 @@ ja: label_column_in_backlog: "バックログの列" label_points_burn_down: "ダウン" label_points_burn_up: "アップ" - label_select_type: "タイプを選択" - label_select_types: "タイプを選択" - label_selected_type: "タイプを選択" - label_selected_types: "タイプを選択" label_sprint_impediments: "スプリント障害事項" label_task_board: "かんばん" permission_view_master_backlog: "マスター バックログの表示" diff --git a/modules/backlogs/config/locales/crowdin/ka.yml b/modules/backlogs/config/locales/crowdin/ka.yml index 9f6ee61e840..9939b839c08 100644 --- a/modules/backlogs/config/locales/crowdin/ka.yml +++ b/modules/backlogs/config/locales/crowdin/ka.yml @@ -120,10 +120,6 @@ ka: label_column_in_backlog: "Column in backlog" label_points_burn_down: "ქვემოთ" label_points_burn_up: "ზემოთ" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "Sprint Impediments" label_task_board: "ამოცანების დაფა" permission_view_master_backlog: "View master backlog" diff --git a/modules/backlogs/config/locales/crowdin/kk.yml b/modules/backlogs/config/locales/crowdin/kk.yml index 8acb224e27b..584b33393b7 100644 --- a/modules/backlogs/config/locales/crowdin/kk.yml +++ b/modules/backlogs/config/locales/crowdin/kk.yml @@ -120,10 +120,6 @@ kk: label_column_in_backlog: "Column in backlog" label_points_burn_down: "Down" label_points_burn_up: "Up" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "Sprint Impediments" label_task_board: "Task board" permission_view_master_backlog: "View master backlog" diff --git a/modules/backlogs/config/locales/crowdin/ko.yml b/modules/backlogs/config/locales/crowdin/ko.yml index 1377245e15c..4bc4ff2c2c2 100644 --- a/modules/backlogs/config/locales/crowdin/ko.yml +++ b/modules/backlogs/config/locales/crowdin/ko.yml @@ -118,10 +118,6 @@ ko: label_column_in_backlog: "백로그의 열" label_points_burn_down: "아래" label_points_burn_up: "위" - label_select_type: "유형 선택" - label_select_types: "유형 선택" - label_selected_type: "선택된 유형" - label_selected_types: "선택된 유형" label_sprint_impediments: "스프린트 제한" label_task_board: "작업 보드" permission_view_master_backlog: "마스터 백로그 보기" diff --git a/modules/backlogs/config/locales/crowdin/lt.yml b/modules/backlogs/config/locales/crowdin/lt.yml index 0c7f8d9c46c..47f4923d387 100644 --- a/modules/backlogs/config/locales/crowdin/lt.yml +++ b/modules/backlogs/config/locales/crowdin/lt.yml @@ -124,10 +124,6 @@ lt: label_column_in_backlog: "Stulpelis darbų sąraše" label_points_burn_down: "Žemyn" label_points_burn_up: "Aukštyn" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "Sprinto trukdžiai" label_task_board: "Užduočių lenta" permission_view_master_backlog: "Peržiūrėti pagrindinį darbų sąrašą" diff --git a/modules/backlogs/config/locales/crowdin/lv.yml b/modules/backlogs/config/locales/crowdin/lv.yml index f43bbbecbd7..2adefaeaf7a 100644 --- a/modules/backlogs/config/locales/crowdin/lv.yml +++ b/modules/backlogs/config/locales/crowdin/lv.yml @@ -122,10 +122,6 @@ lv: label_column_in_backlog: "Atlikušo darbu backlog" label_points_burn_down: "Lejup" label_points_burn_up: "Augšup" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "Sprinta šķēršļi" label_task_board: "Pieteikumu tāfele" permission_view_master_backlog: "Skatīt visus nepabeigtos darbus" diff --git a/modules/backlogs/config/locales/crowdin/mn.yml b/modules/backlogs/config/locales/crowdin/mn.yml index 411d5f0b2a4..3bc7baea066 100644 --- a/modules/backlogs/config/locales/crowdin/mn.yml +++ b/modules/backlogs/config/locales/crowdin/mn.yml @@ -120,10 +120,6 @@ mn: label_column_in_backlog: "Column in backlog" label_points_burn_down: "Down" label_points_burn_up: "Up" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "Sprint Impediments" label_task_board: "Task board" permission_view_master_backlog: "View master backlog" diff --git a/modules/backlogs/config/locales/crowdin/ms.yml b/modules/backlogs/config/locales/crowdin/ms.yml index b96c09c8df9..d3ea60be5c1 100644 --- a/modules/backlogs/config/locales/crowdin/ms.yml +++ b/modules/backlogs/config/locales/crowdin/ms.yml @@ -118,10 +118,6 @@ ms: label_column_in_backlog: "Kolum dalam tunggakan" label_points_burn_down: "Bawah" label_points_burn_up: "Atas" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "Halangan Pecutan" label_task_board: "Papan tugasan" permission_view_master_backlog: "Paparkan tunggakan utama" diff --git a/modules/backlogs/config/locales/crowdin/ne.yml b/modules/backlogs/config/locales/crowdin/ne.yml index 05c787a3b54..5b907ae6b84 100644 --- a/modules/backlogs/config/locales/crowdin/ne.yml +++ b/modules/backlogs/config/locales/crowdin/ne.yml @@ -120,10 +120,6 @@ ne: label_column_in_backlog: "Column in backlog" label_points_burn_down: "Down" label_points_burn_up: "Up" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "Sprint Impediments" label_task_board: "Task board" permission_view_master_backlog: "View master backlog" diff --git a/modules/backlogs/config/locales/crowdin/nl.yml b/modules/backlogs/config/locales/crowdin/nl.yml index a8a437bd8b6..50c125516ba 100644 --- a/modules/backlogs/config/locales/crowdin/nl.yml +++ b/modules/backlogs/config/locales/crowdin/nl.yml @@ -120,10 +120,6 @@ nl: label_column_in_backlog: "Kolom in achterstand" label_points_burn_down: "Omlaag" label_points_burn_up: "Omhoog" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "Sprint Obstakels" label_task_board: "Taakbord" permission_view_master_backlog: "Toon Máster Backlog" diff --git a/modules/backlogs/config/locales/crowdin/no.yml b/modules/backlogs/config/locales/crowdin/no.yml index b978a424d7a..d463b97f8e9 100644 --- a/modules/backlogs/config/locales/crowdin/no.yml +++ b/modules/backlogs/config/locales/crowdin/no.yml @@ -120,10 +120,6 @@ label_column_in_backlog: "Kolonne i forsinkelse" label_points_burn_down: "Ned" label_points_burn_up: "Opp" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "Hindring i etappe" label_task_board: "Oppgavetavle" permission_view_master_backlog: "Vis master forsinkelse" diff --git a/modules/backlogs/config/locales/crowdin/pl.yml b/modules/backlogs/config/locales/crowdin/pl.yml index 0e3b923e5d2..b113203ab32 100644 --- a/modules/backlogs/config/locales/crowdin/pl.yml +++ b/modules/backlogs/config/locales/crowdin/pl.yml @@ -124,10 +124,6 @@ pl: label_column_in_backlog: "Kolumna w backlogu" label_points_burn_down: "W dół" label_points_burn_up: "W górę" - label_select_type: "Wybierz typ" - label_select_types: "Wybierz typy" - label_selected_type: "Wybrany typ" - label_selected_types: "Wybrane typy" label_sprint_impediments: "Przeszkody sprintu" label_task_board: "Panel zadań" permission_view_master_backlog: "Wyświetl master backlog" diff --git a/modules/backlogs/config/locales/crowdin/pt-BR.yml b/modules/backlogs/config/locales/crowdin/pt-BR.yml index 5366dd9fa88..0bb6ae34367 100644 --- a/modules/backlogs/config/locales/crowdin/pt-BR.yml +++ b/modules/backlogs/config/locales/crowdin/pt-BR.yml @@ -120,10 +120,6 @@ pt-BR: label_column_in_backlog: "Coluna no backlog" label_points_burn_down: "Abaixo" label_points_burn_up: "Acima" - label_select_type: "Selecione um tipo" - label_select_types: "Selecionar tipos" - label_selected_type: "Tipo selecionado" - label_selected_types: "Tipos selecionados" label_sprint_impediments: "Impedimentos da Sprint" label_task_board: "Quadro de tarefas" permission_view_master_backlog: "Visualizar backlog principal" diff --git a/modules/backlogs/config/locales/crowdin/pt-PT.yml b/modules/backlogs/config/locales/crowdin/pt-PT.yml index e6925ed37b3..2fb81ed7f7b 100644 --- a/modules/backlogs/config/locales/crowdin/pt-PT.yml +++ b/modules/backlogs/config/locales/crowdin/pt-PT.yml @@ -120,10 +120,6 @@ pt-PT: label_column_in_backlog: "Coluna no backlog" label_points_burn_down: "Abaixo" label_points_burn_up: "Acima" - label_select_type: "Selecionar um tipo" - label_select_types: "Selecionar tipos" - label_selected_type: "Tipo selecionado" - label_selected_types: "Tipos selecionados" label_sprint_impediments: "Impedimentos de Sprint" label_task_board: "Quadro de tarefas" permission_view_master_backlog: "Ver o backlog principal" diff --git a/modules/backlogs/config/locales/crowdin/ro.yml b/modules/backlogs/config/locales/crowdin/ro.yml index 55438a8b9f1..ecc7478350d 100644 --- a/modules/backlogs/config/locales/crowdin/ro.yml +++ b/modules/backlogs/config/locales/crowdin/ro.yml @@ -122,10 +122,6 @@ ro: label_column_in_backlog: "Coloană în backlog" label_points_burn_down: "Jos" label_points_burn_up: "Sus" - label_select_type: "Selectează un tip" - label_select_types: "Selectează tipuri" - label_selected_type: "Tip selectat" - label_selected_types: "Tipuri selectate" label_sprint_impediments: "Impedimentele Sprint" label_task_board: "Tablă de sarcini" permission_view_master_backlog: "Vizualizare master backlog" diff --git a/modules/backlogs/config/locales/crowdin/ru.yml b/modules/backlogs/config/locales/crowdin/ru.yml index 0c56d8c31e1..45dc34f3534 100644 --- a/modules/backlogs/config/locales/crowdin/ru.yml +++ b/modules/backlogs/config/locales/crowdin/ru.yml @@ -124,10 +124,6 @@ ru: label_column_in_backlog: "Колонка в бэклоге" label_points_burn_down: "Вниз" label_points_burn_up: "Вверх" - label_select_type: "Выберите тип" - label_select_types: "Выберите типы" - label_selected_type: "Выбранный тип" - label_selected_types: "Выбранные типы" label_sprint_impediments: "Препятствия спринта" label_task_board: "Панель задач" permission_view_master_backlog: "Просмотреть главную невыполненную работу" diff --git a/modules/backlogs/config/locales/crowdin/rw.yml b/modules/backlogs/config/locales/crowdin/rw.yml index e3a3ad332a2..83a8442ce8f 100644 --- a/modules/backlogs/config/locales/crowdin/rw.yml +++ b/modules/backlogs/config/locales/crowdin/rw.yml @@ -120,10 +120,6 @@ rw: label_column_in_backlog: "Column in backlog" label_points_burn_down: "Down" label_points_burn_up: "Up" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "Sprint Impediments" label_task_board: "Task board" permission_view_master_backlog: "View master backlog" diff --git a/modules/backlogs/config/locales/crowdin/si.yml b/modules/backlogs/config/locales/crowdin/si.yml index c517654e8a5..f2789c8f7b8 100644 --- a/modules/backlogs/config/locales/crowdin/si.yml +++ b/modules/backlogs/config/locales/crowdin/si.yml @@ -120,10 +120,6 @@ si: label_column_in_backlog: "පසුබිම තුළ තීරුව" label_points_burn_down: "පහළට" label_points_burn_up: "ඉහළට" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "ස්ප්රින්ට් බාධාවන්" label_task_board: "කාර්ය මණ්ඩලය" permission_view_master_backlog: "ස්වාමියා බැක්ලොග් දැක්ම" diff --git a/modules/backlogs/config/locales/crowdin/sk.yml b/modules/backlogs/config/locales/crowdin/sk.yml index 1627cf6632e..8cca23a7bcf 100644 --- a/modules/backlogs/config/locales/crowdin/sk.yml +++ b/modules/backlogs/config/locales/crowdin/sk.yml @@ -124,10 +124,6 @@ sk: label_column_in_backlog: "Column in backlog" label_points_burn_down: "Nadol" label_points_burn_up: "Nahor" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "Sprint Impediments" label_task_board: "Task board" permission_view_master_backlog: "View master backlog" diff --git a/modules/backlogs/config/locales/crowdin/sl.yml b/modules/backlogs/config/locales/crowdin/sl.yml index c1ce0980904..75de0f0bad3 100644 --- a/modules/backlogs/config/locales/crowdin/sl.yml +++ b/modules/backlogs/config/locales/crowdin/sl.yml @@ -124,10 +124,6 @@ sl: label_column_in_backlog: "Stolpec v zaostanku" label_points_burn_down: "Navzdol" label_points_burn_up: "Navzgor" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "Motnje sprinta" label_task_board: "Tabla opravil" permission_view_master_backlog: "glavni zaostanek prejšnje poizvedbe" diff --git a/modules/backlogs/config/locales/crowdin/sr.yml b/modules/backlogs/config/locales/crowdin/sr.yml index 9726767397b..58043dd441a 100644 --- a/modules/backlogs/config/locales/crowdin/sr.yml +++ b/modules/backlogs/config/locales/crowdin/sr.yml @@ -122,10 +122,6 @@ sr: label_column_in_backlog: "Column in backlog" label_points_burn_down: "Down" label_points_burn_up: "Up" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "Sprint Impediments" label_task_board: "Task board" permission_view_master_backlog: "View master backlog" diff --git a/modules/backlogs/config/locales/crowdin/sv.yml b/modules/backlogs/config/locales/crowdin/sv.yml index 072966d4fe3..399d2b9b27f 100644 --- a/modules/backlogs/config/locales/crowdin/sv.yml +++ b/modules/backlogs/config/locales/crowdin/sv.yml @@ -120,10 +120,6 @@ sv: label_column_in_backlog: "Kolumn i backlog" label_points_burn_down: "Ner" label_points_burn_up: "Upp" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "Sprint hinder" label_task_board: "Aktivitetstavla" permission_view_master_backlog: "Visa master backlog" diff --git a/modules/backlogs/config/locales/crowdin/th.yml b/modules/backlogs/config/locales/crowdin/th.yml index c0cf5665688..a29b57b0a7b 100644 --- a/modules/backlogs/config/locales/crowdin/th.yml +++ b/modules/backlogs/config/locales/crowdin/th.yml @@ -118,10 +118,6 @@ th: label_column_in_backlog: "Column in backlog" label_points_burn_down: "Down" label_points_burn_up: "Up" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "Sprint Impediments" label_task_board: "Task board" permission_view_master_backlog: "View master backlog" diff --git a/modules/backlogs/config/locales/crowdin/tr.yml b/modules/backlogs/config/locales/crowdin/tr.yml index 8229354b412..5e8a2f5241e 100644 --- a/modules/backlogs/config/locales/crowdin/tr.yml +++ b/modules/backlogs/config/locales/crowdin/tr.yml @@ -120,10 +120,6 @@ tr: label_column_in_backlog: "Sütununda birikim var" label_points_burn_down: "Aşağı" label_points_burn_up: "Yukarı" - label_select_type: "Bir tür seçin" - label_select_types: "Türleri seçin" - label_selected_type: "Seçili tür" - label_selected_types: "Seçilen türler" label_sprint_impediments: "Sprint Engelleri" label_task_board: "Görev panosu" permission_view_master_backlog: "Ana bekleme günlüğünü görüntüleme" diff --git a/modules/backlogs/config/locales/crowdin/uk.yml b/modules/backlogs/config/locales/crowdin/uk.yml index 2a33483277a..9384bd25a78 100644 --- a/modules/backlogs/config/locales/crowdin/uk.yml +++ b/modules/backlogs/config/locales/crowdin/uk.yml @@ -124,10 +124,6 @@ uk: label_column_in_backlog: "Стовпець у backlog-у" label_points_burn_down: "Вниз" label_points_burn_up: "Вгору" - label_select_type: "Виберіть тип" - label_select_types: "Виберіть типи" - label_selected_type: "Вибраний тип" - label_selected_types: "Вибрані типи" label_sprint_impediments: "Перешкоди спринту" label_task_board: "Дошка завдань" permission_view_master_backlog: "Перегляд головного backlog-у" diff --git a/modules/backlogs/config/locales/crowdin/uz.yml b/modules/backlogs/config/locales/crowdin/uz.yml index a1817f6a842..1d784af9e97 100644 --- a/modules/backlogs/config/locales/crowdin/uz.yml +++ b/modules/backlogs/config/locales/crowdin/uz.yml @@ -120,10 +120,6 @@ uz: label_column_in_backlog: "Column in backlog" label_points_burn_down: "Down" label_points_burn_up: "Up" - label_select_type: "Select a type" - label_select_types: "Select types" - label_selected_type: "Selected type" - label_selected_types: "Selected types" label_sprint_impediments: "Sprint Impediments" label_task_board: "Task board" permission_view_master_backlog: "View master backlog" diff --git a/modules/backlogs/config/locales/crowdin/vi.yml b/modules/backlogs/config/locales/crowdin/vi.yml index c6f646d6356..4c8ca2e4f8f 100644 --- a/modules/backlogs/config/locales/crowdin/vi.yml +++ b/modules/backlogs/config/locales/crowdin/vi.yml @@ -118,10 +118,6 @@ vi: label_column_in_backlog: "Cột tồn đọng" label_points_burn_down: "Xuống" label_points_burn_up: "lên" - label_select_type: "Chọn một loại" - label_select_types: "Chọn loại" - label_selected_type: "Loại đã chọn" - label_selected_types: "Các loại đã chọn" label_sprint_impediments: "Trở ngại nước rút" label_task_board: "Bảng nhiệm vụ" permission_view_master_backlog: "Xem tồn đọng chính" diff --git a/modules/backlogs/config/locales/crowdin/zh-CN.yml b/modules/backlogs/config/locales/crowdin/zh-CN.yml index 9751f44c9de..ed193584a6c 100644 --- a/modules/backlogs/config/locales/crowdin/zh-CN.yml +++ b/modules/backlogs/config/locales/crowdin/zh-CN.yml @@ -118,10 +118,6 @@ zh-CN: label_column_in_backlog: "待办清单中的列" label_points_burn_down: "减少" label_points_burn_up: "增加" - label_select_type: "选择类型" - label_select_types: "选择类型" - label_selected_type: "所选类型" - label_selected_types: "所选类型" label_sprint_impediments: "冲刺 (sprint) 障碍" label_task_board: "任务板" permission_view_master_backlog: "查看主待办清单" diff --git a/modules/backlogs/config/locales/crowdin/zh-TW.yml b/modules/backlogs/config/locales/crowdin/zh-TW.yml index e2cd4ebf04e..36e5af479ba 100644 --- a/modules/backlogs/config/locales/crowdin/zh-TW.yml +++ b/modules/backlogs/config/locales/crowdin/zh-TW.yml @@ -118,10 +118,6 @@ zh-TW: label_column_in_backlog: "待辦事項的欄位" label_points_burn_down: "減少" label_points_burn_up: "增加" - label_select_type: "選擇類型" - label_select_types: "選擇類型" - label_selected_type: "所選類型" - label_selected_types: "所選類型" label_sprint_impediments: "進度阻礙" label_task_board: "任務看板" permission_view_master_backlog: "檢視主待辦事項" diff --git a/modules/grids/config/locales/crowdin/js-af.yml b/modules/grids/config/locales/crowdin/js-af.yml index fece38fef28..9d0b24f2c0c 100644 --- a/modules/grids/config/locales/crowdin/js-af.yml +++ b/modules/grids/config/locales/crowdin/js-af.yml @@ -27,8 +27,6 @@ af: not_set: 'Not set' finished: 'Finished' discontinued: 'Discontinued' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-ar.yml b/modules/grids/config/locales/crowdin/js-ar.yml index 62ad4abc240..6d2c52ffdf2 100644 --- a/modules/grids/config/locales/crowdin/js-ar.yml +++ b/modules/grids/config/locales/crowdin/js-ar.yml @@ -27,8 +27,6 @@ ar: not_set: 'Not set' finished: 'مكتمل' discontinued: 'Discontinued' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-az.yml b/modules/grids/config/locales/crowdin/js-az.yml index a6ffc82b41f..d40e1876c86 100644 --- a/modules/grids/config/locales/crowdin/js-az.yml +++ b/modules/grids/config/locales/crowdin/js-az.yml @@ -27,8 +27,6 @@ az: not_set: 'Not set' finished: 'Finished' discontinued: 'Discontinued' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-be.yml b/modules/grids/config/locales/crowdin/js-be.yml index 5ff0b517eed..6c7bbdab722 100644 --- a/modules/grids/config/locales/crowdin/js-be.yml +++ b/modules/grids/config/locales/crowdin/js-be.yml @@ -27,8 +27,6 @@ be: not_set: 'Not set' finished: 'Finished' discontinued: 'Discontinued' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-bg.yml b/modules/grids/config/locales/crowdin/js-bg.yml index f9db9d3f32c..01ce10c4f94 100644 --- a/modules/grids/config/locales/crowdin/js-bg.yml +++ b/modules/grids/config/locales/crowdin/js-bg.yml @@ -27,8 +27,6 @@ bg: not_set: 'Не е зададено' finished: 'Завършено' discontinued: 'Discontinued' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-ca.yml b/modules/grids/config/locales/crowdin/js-ca.yml index 098ad24f4d9..2704921e982 100644 --- a/modules/grids/config/locales/crowdin/js-ca.yml +++ b/modules/grids/config/locales/crowdin/js-ca.yml @@ -27,8 +27,6 @@ ca: not_set: 'No configurat' finished: 'Finalitzat' discontinued: 'Discontinuat' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-ckb-IR.yml b/modules/grids/config/locales/crowdin/js-ckb-IR.yml index 84757f15545..73cc41b0900 100644 --- a/modules/grids/config/locales/crowdin/js-ckb-IR.yml +++ b/modules/grids/config/locales/crowdin/js-ckb-IR.yml @@ -27,8 +27,6 @@ ckb-IR: not_set: 'Not set' finished: 'Finished' discontinued: 'Discontinued' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-cs.yml b/modules/grids/config/locales/crowdin/js-cs.yml index 839a18cc98c..7b5ae502a0a 100644 --- a/modules/grids/config/locales/crowdin/js-cs.yml +++ b/modules/grids/config/locales/crowdin/js-cs.yml @@ -27,8 +27,6 @@ cs: not_set: 'Nenastaveno' finished: 'Dokončeno' discontinued: 'Zrušeno' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-da.yml b/modules/grids/config/locales/crowdin/js-da.yml index 1baa7917a2f..4d406f0573a 100644 --- a/modules/grids/config/locales/crowdin/js-da.yml +++ b/modules/grids/config/locales/crowdin/js-da.yml @@ -27,8 +27,6 @@ da: not_set: 'Ikke angivet' finished: 'Afsluttet' discontinued: 'Discontinued' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-de.yml b/modules/grids/config/locales/crowdin/js-de.yml index 0576461a1a5..009811e6bea 100644 --- a/modules/grids/config/locales/crowdin/js-de.yml +++ b/modules/grids/config/locales/crowdin/js-de.yml @@ -27,8 +27,6 @@ de: not_set: 'Nicht gesetzt' finished: 'Abgeschlossen' discontinued: 'Eingestellt' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Unterelemente' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-el.yml b/modules/grids/config/locales/crowdin/js-el.yml index ec53c09c34b..9cd200739a7 100644 --- a/modules/grids/config/locales/crowdin/js-el.yml +++ b/modules/grids/config/locales/crowdin/js-el.yml @@ -27,8 +27,6 @@ el: not_set: 'Μη ορισμένο' finished: 'Ολοκληρωμένο' discontinued: 'Discontinued' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-eo.yml b/modules/grids/config/locales/crowdin/js-eo.yml index 67333cdad7f..4a45e7e3936 100644 --- a/modules/grids/config/locales/crowdin/js-eo.yml +++ b/modules/grids/config/locales/crowdin/js-eo.yml @@ -27,8 +27,6 @@ eo: not_set: 'Ne agordita' finished: 'Finita' discontinued: 'Discontinued' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-es.yml b/modules/grids/config/locales/crowdin/js-es.yml index d0624977975..d78b6ffdfd7 100644 --- a/modules/grids/config/locales/crowdin/js-es.yml +++ b/modules/grids/config/locales/crowdin/js-es.yml @@ -27,8 +27,6 @@ es: not_set: 'No establecido' finished: 'Terminado' discontinued: 'Discontinuado' - project_status_beta: - title: 'Estado (BETA)' subprojects: title: 'Subelementos' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-et.yml b/modules/grids/config/locales/crowdin/js-et.yml index 992d9c59bff..cc0d7a041e8 100644 --- a/modules/grids/config/locales/crowdin/js-et.yml +++ b/modules/grids/config/locales/crowdin/js-et.yml @@ -27,8 +27,6 @@ et: not_set: 'Not set' finished: 'Lõpetatud' discontinued: 'Lopetatud' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-eu.yml b/modules/grids/config/locales/crowdin/js-eu.yml index 3fda6b0b0e7..e79f43da6c6 100644 --- a/modules/grids/config/locales/crowdin/js-eu.yml +++ b/modules/grids/config/locales/crowdin/js-eu.yml @@ -27,8 +27,6 @@ eu: not_set: 'Not set' finished: 'Finished' discontinued: 'Discontinued' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-fa.yml b/modules/grids/config/locales/crowdin/js-fa.yml index 1692cbcb7bc..55eb5a54b68 100644 --- a/modules/grids/config/locales/crowdin/js-fa.yml +++ b/modules/grids/config/locales/crowdin/js-fa.yml @@ -27,8 +27,6 @@ fa: not_set: 'تنظیم نشده' finished: 'پایان یافته' discontinued: 'Discontinued' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-fi.yml b/modules/grids/config/locales/crowdin/js-fi.yml index d2bbf504c4a..ad36798e96b 100644 --- a/modules/grids/config/locales/crowdin/js-fi.yml +++ b/modules/grids/config/locales/crowdin/js-fi.yml @@ -27,8 +27,6 @@ fi: not_set: 'Määrittelemätön' finished: 'Valmis' discontinued: 'Discontinued' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-fil.yml b/modules/grids/config/locales/crowdin/js-fil.yml index 931843799c3..19c3c29acf3 100644 --- a/modules/grids/config/locales/crowdin/js-fil.yml +++ b/modules/grids/config/locales/crowdin/js-fil.yml @@ -27,8 +27,6 @@ fil: not_set: 'Not set' finished: 'Finished' discontinued: 'Discontinued' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-fr.yml b/modules/grids/config/locales/crowdin/js-fr.yml index 5dd0bf4a240..1ab28e492f6 100644 --- a/modules/grids/config/locales/crowdin/js-fr.yml +++ b/modules/grids/config/locales/crowdin/js-fr.yml @@ -27,8 +27,6 @@ fr: not_set: 'Non défini' finished: 'Terminé' discontinued: 'Interrompu' - project_status_beta: - title: 'Statut (BETA)' subprojects: title: 'Sous-éléments' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-he.yml b/modules/grids/config/locales/crowdin/js-he.yml index 21c5b40eaae..16dabeeb7d5 100644 --- a/modules/grids/config/locales/crowdin/js-he.yml +++ b/modules/grids/config/locales/crowdin/js-he.yml @@ -27,8 +27,6 @@ he: not_set: 'Not set' finished: 'הסתיים' discontinued: 'Discontinued' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-hi.yml b/modules/grids/config/locales/crowdin/js-hi.yml index cb747830d95..fd94e01b64e 100644 --- a/modules/grids/config/locales/crowdin/js-hi.yml +++ b/modules/grids/config/locales/crowdin/js-hi.yml @@ -27,8 +27,6 @@ hi: not_set: 'Not set' finished: 'Finished' discontinued: 'Discontinued' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-hr.yml b/modules/grids/config/locales/crowdin/js-hr.yml index 9831ce57517..09d77e69328 100644 --- a/modules/grids/config/locales/crowdin/js-hr.yml +++ b/modules/grids/config/locales/crowdin/js-hr.yml @@ -27,8 +27,6 @@ hr: not_set: 'Not set' finished: 'Završeno' discontinued: 'Discontinued' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-hu.yml b/modules/grids/config/locales/crowdin/js-hu.yml index 7e86c40c222..b7930a663eb 100644 --- a/modules/grids/config/locales/crowdin/js-hu.yml +++ b/modules/grids/config/locales/crowdin/js-hu.yml @@ -27,8 +27,6 @@ hu: not_set: 'Nincs beállítva' finished: 'Befejezett' discontinued: 'Megszakított' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-id.yml b/modules/grids/config/locales/crowdin/js-id.yml index f57115cda88..0f42a308623 100644 --- a/modules/grids/config/locales/crowdin/js-id.yml +++ b/modules/grids/config/locales/crowdin/js-id.yml @@ -27,8 +27,6 @@ id: not_set: 'Belum diatur' finished: 'Selesai' discontinued: 'Dihentikan' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitem' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-it.yml b/modules/grids/config/locales/crowdin/js-it.yml index 96c96d71f8d..a1d68b38378 100644 --- a/modules/grids/config/locales/crowdin/js-it.yml +++ b/modules/grids/config/locales/crowdin/js-it.yml @@ -27,8 +27,6 @@ it: not_set: 'Non impostato' finished: 'Terminato' discontinued: 'Interrotto' - project_status_beta: - title: 'Stato (BETA)' subprojects: title: 'Sottoelementi' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-ja.yml b/modules/grids/config/locales/crowdin/js-ja.yml index 86144f15217..c11bcc8a114 100644 --- a/modules/grids/config/locales/crowdin/js-ja.yml +++ b/modules/grids/config/locales/crowdin/js-ja.yml @@ -27,8 +27,6 @@ ja: not_set: '未設定' finished: '完了' discontinued: '中止' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-ka.yml b/modules/grids/config/locales/crowdin/js-ka.yml index 88e8a4bb51b..bb3d41d5cd0 100644 --- a/modules/grids/config/locales/crowdin/js-ka.yml +++ b/modules/grids/config/locales/crowdin/js-ka.yml @@ -27,8 +27,6 @@ ka: not_set: 'არ არის დაყენებული' finished: 'დასრულებულია' discontinued: 'დასრულდა' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-kk.yml b/modules/grids/config/locales/crowdin/js-kk.yml index 81684845e83..e07cb15980d 100644 --- a/modules/grids/config/locales/crowdin/js-kk.yml +++ b/modules/grids/config/locales/crowdin/js-kk.yml @@ -27,8 +27,6 @@ kk: not_set: 'Not set' finished: 'Finished' discontinued: 'Discontinued' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-ko.yml b/modules/grids/config/locales/crowdin/js-ko.yml index 624ad02d741..b83d0fbc73c 100644 --- a/modules/grids/config/locales/crowdin/js-ko.yml +++ b/modules/grids/config/locales/crowdin/js-ko.yml @@ -27,8 +27,6 @@ ko: not_set: '설정되지 않음' finished: '마침' discontinued: '중단됨' - project_status_beta: - title: '상태(BETA)' subprojects: title: '하위 항목' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-lt.yml b/modules/grids/config/locales/crowdin/js-lt.yml index e3ff11ef0bf..f6bd7c09992 100644 --- a/modules/grids/config/locales/crowdin/js-lt.yml +++ b/modules/grids/config/locales/crowdin/js-lt.yml @@ -27,8 +27,6 @@ lt: not_set: 'Nenustatyta' finished: 'Baigta' discontinued: 'Nutrauktas' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-lv.yml b/modules/grids/config/locales/crowdin/js-lv.yml index f2a0824429e..3af5036a4e3 100644 --- a/modules/grids/config/locales/crowdin/js-lv.yml +++ b/modules/grids/config/locales/crowdin/js-lv.yml @@ -27,8 +27,6 @@ lv: not_set: 'Not set' finished: 'Pabeigts' discontinued: 'Discontinued' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-mn.yml b/modules/grids/config/locales/crowdin/js-mn.yml index 38d8bff23ae..66c4049d533 100644 --- a/modules/grids/config/locales/crowdin/js-mn.yml +++ b/modules/grids/config/locales/crowdin/js-mn.yml @@ -27,8 +27,6 @@ mn: not_set: 'Not set' finished: 'Finished' discontinued: 'Discontinued' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-ms.yml b/modules/grids/config/locales/crowdin/js-ms.yml index 79f2803a1fb..76216aa4f2a 100644 --- a/modules/grids/config/locales/crowdin/js-ms.yml +++ b/modules/grids/config/locales/crowdin/js-ms.yml @@ -27,8 +27,6 @@ ms: not_set: 'Belum ditetapkan' finished: 'Telah selesai' discontinued: 'Dihentikan' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-ne.yml b/modules/grids/config/locales/crowdin/js-ne.yml index 0cef1ea78df..2cd4b3b7ae1 100644 --- a/modules/grids/config/locales/crowdin/js-ne.yml +++ b/modules/grids/config/locales/crowdin/js-ne.yml @@ -27,8 +27,6 @@ ne: not_set: 'Not set' finished: 'Finished' discontinued: 'Discontinued' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-nl.yml b/modules/grids/config/locales/crowdin/js-nl.yml index c9b85083361..43ec10e38b1 100644 --- a/modules/grids/config/locales/crowdin/js-nl.yml +++ b/modules/grids/config/locales/crowdin/js-nl.yml @@ -27,8 +27,6 @@ nl: not_set: 'Niet ingesteld' finished: 'Afgewerkt' discontinued: 'Stopgezet' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-no.yml b/modules/grids/config/locales/crowdin/js-no.yml index 6b950484425..3f706b1042d 100644 --- a/modules/grids/config/locales/crowdin/js-no.yml +++ b/modules/grids/config/locales/crowdin/js-no.yml @@ -27,8 +27,6 @@ not_set: 'Ikke angitt' finished: 'Fullført' discontinued: 'Utløpt' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-pl.yml b/modules/grids/config/locales/crowdin/js-pl.yml index c91e19830d4..0587ceccb3b 100644 --- a/modules/grids/config/locales/crowdin/js-pl.yml +++ b/modules/grids/config/locales/crowdin/js-pl.yml @@ -27,8 +27,6 @@ pl: not_set: 'Nie ustawione' finished: 'Zakończone' discontinued: 'Przerwane' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Podppzycje' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-pt-BR.yml b/modules/grids/config/locales/crowdin/js-pt-BR.yml index a9875eaeeae..bcf3ac0830b 100644 --- a/modules/grids/config/locales/crowdin/js-pt-BR.yml +++ b/modules/grids/config/locales/crowdin/js-pt-BR.yml @@ -27,8 +27,6 @@ pt-BR: not_set: 'Não definido' finished: 'Finalizado' discontinued: 'Descontinuado' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitens' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-pt-PT.yml b/modules/grids/config/locales/crowdin/js-pt-PT.yml index 8833105c50d..681e724dd9c 100644 --- a/modules/grids/config/locales/crowdin/js-pt-PT.yml +++ b/modules/grids/config/locales/crowdin/js-pt-PT.yml @@ -27,8 +27,6 @@ pt-PT: not_set: 'Não definido' finished: 'Terminado' discontinued: 'Descontinuado' - project_status_beta: - title: 'Estado (BETA)' subprojects: title: 'Sub-elementos' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-ro.yml b/modules/grids/config/locales/crowdin/js-ro.yml index 159d93c7387..6a14e4614cf 100644 --- a/modules/grids/config/locales/crowdin/js-ro.yml +++ b/modules/grids/config/locales/crowdin/js-ro.yml @@ -27,8 +27,6 @@ ro: not_set: 'Nesetat' finished: 'Finalizat' discontinued: 'Anulat' - project_status_beta: - title: 'Stare (BETA)' subprojects: title: 'Subelemente' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-ru.yml b/modules/grids/config/locales/crowdin/js-ru.yml index b0389aefe39..a12b0db4195 100644 --- a/modules/grids/config/locales/crowdin/js-ru.yml +++ b/modules/grids/config/locales/crowdin/js-ru.yml @@ -27,8 +27,6 @@ ru: not_set: 'Не задано' finished: 'Завершен' discontinued: 'Прекращен' - project_status_beta: - title: 'Статус (BETA)' subprojects: title: 'Подпроекты' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-rw.yml b/modules/grids/config/locales/crowdin/js-rw.yml index 38bd192e674..895dc79c4d8 100644 --- a/modules/grids/config/locales/crowdin/js-rw.yml +++ b/modules/grids/config/locales/crowdin/js-rw.yml @@ -27,8 +27,6 @@ rw: not_set: 'Not set' finished: 'Finished' discontinued: 'Discontinued' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-si.yml b/modules/grids/config/locales/crowdin/js-si.yml index 73496923f2d..496fc7d3707 100644 --- a/modules/grids/config/locales/crowdin/js-si.yml +++ b/modules/grids/config/locales/crowdin/js-si.yml @@ -27,8 +27,6 @@ si: not_set: 'Not set' finished: 'Finished' discontinued: 'Discontinued' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-sk.yml b/modules/grids/config/locales/crowdin/js-sk.yml index 0770731eaf5..386316bcf36 100644 --- a/modules/grids/config/locales/crowdin/js-sk.yml +++ b/modules/grids/config/locales/crowdin/js-sk.yml @@ -27,8 +27,6 @@ sk: not_set: 'Not set' finished: 'Dokončené' discontinued: 'Discontinued' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-sl.yml b/modules/grids/config/locales/crowdin/js-sl.yml index 537f6649617..eb1b1b36df1 100644 --- a/modules/grids/config/locales/crowdin/js-sl.yml +++ b/modules/grids/config/locales/crowdin/js-sl.yml @@ -27,8 +27,6 @@ sl: not_set: 'Ni nastavljeno' finished: 'Končano' discontinued: 'Opuščeno' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-sr.yml b/modules/grids/config/locales/crowdin/js-sr.yml index 5f4e32b361a..6fde69da13c 100644 --- a/modules/grids/config/locales/crowdin/js-sr.yml +++ b/modules/grids/config/locales/crowdin/js-sr.yml @@ -27,8 +27,6 @@ sr: not_set: 'Not set' finished: 'Finished' discontinued: 'Discontinued' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-sv.yml b/modules/grids/config/locales/crowdin/js-sv.yml index a0ca2638757..7a040df835a 100644 --- a/modules/grids/config/locales/crowdin/js-sv.yml +++ b/modules/grids/config/locales/crowdin/js-sv.yml @@ -27,8 +27,6 @@ sv: not_set: 'Ej inställd' finished: 'Avslutad' discontinued: 'Utgången' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Underpunkter' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-th.yml b/modules/grids/config/locales/crowdin/js-th.yml index b88741fafc6..10d9560f3af 100644 --- a/modules/grids/config/locales/crowdin/js-th.yml +++ b/modules/grids/config/locales/crowdin/js-th.yml @@ -27,8 +27,6 @@ th: not_set: 'Not set' finished: 'Finished' discontinued: 'Discontinued' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-tr.yml b/modules/grids/config/locales/crowdin/js-tr.yml index 224eee3379e..7555593077d 100644 --- a/modules/grids/config/locales/crowdin/js-tr.yml +++ b/modules/grids/config/locales/crowdin/js-tr.yml @@ -27,8 +27,6 @@ tr: not_set: 'Ayarlanmadı' finished: 'Tamamlandı' discontinued: 'Durduruldu' - project_status_beta: - title: 'Durum (BETA)' subprojects: title: 'Alt öğeler' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-uk.yml b/modules/grids/config/locales/crowdin/js-uk.yml index 015c13ca33d..495774ebfbe 100644 --- a/modules/grids/config/locales/crowdin/js-uk.yml +++ b/modules/grids/config/locales/crowdin/js-uk.yml @@ -27,8 +27,6 @@ uk: not_set: 'Не встановлено' finished: 'Завершено' discontinued: 'Припинено' - project_status_beta: - title: 'Статус (BETA)' subprojects: title: 'Піделементи' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-uz.yml b/modules/grids/config/locales/crowdin/js-uz.yml index b129996db6d..015bc7edbfd 100644 --- a/modules/grids/config/locales/crowdin/js-uz.yml +++ b/modules/grids/config/locales/crowdin/js-uz.yml @@ -27,8 +27,6 @@ uz: not_set: 'Not set' finished: 'Finished' discontinued: 'Discontinued' - project_status_beta: - title: 'Status (BETA)' subprojects: title: 'Subitems' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-vi.yml b/modules/grids/config/locales/crowdin/js-vi.yml index 51927944263..132d12d47de 100644 --- a/modules/grids/config/locales/crowdin/js-vi.yml +++ b/modules/grids/config/locales/crowdin/js-vi.yml @@ -27,8 +27,6 @@ vi: not_set: 'Không được thiết lập' finished: 'Đã hoàn thành' discontinued: 'Đã ngừng sản xuất' - project_status_beta: - title: 'Trạng thái (BETA)' subprojects: title: 'mục phụ' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-zh-CN.yml b/modules/grids/config/locales/crowdin/js-zh-CN.yml index fec445018ef..29451641471 100644 --- a/modules/grids/config/locales/crowdin/js-zh-CN.yml +++ b/modules/grids/config/locales/crowdin/js-zh-CN.yml @@ -27,8 +27,6 @@ zh-CN: not_set: '未设置' finished: '已完成' discontinued: '已中断' - project_status_beta: - title: '状态 (BETA)' subprojects: title: '子项目' project_favorites: diff --git a/modules/grids/config/locales/crowdin/js-zh-TW.yml b/modules/grids/config/locales/crowdin/js-zh-TW.yml index 07198ce0f9e..f89b469bd19 100644 --- a/modules/grids/config/locales/crowdin/js-zh-TW.yml +++ b/modules/grids/config/locales/crowdin/js-zh-TW.yml @@ -27,8 +27,6 @@ zh-TW: not_set: '未設定' finished: '已完成' discontinued: '已中止' - project_status_beta: - title: '狀態 (BETA)' subprojects: title: '子項目' project_favorites: From 6ed47760055e98c1ee73832a221cce3067accea3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Feb 2026 05:28:17 -0300 Subject: [PATCH 279/293] Bump meta-tags from 2.22.2 to 2.22.3 (#21966) Bumps [meta-tags](https://github.com/kpumuk/meta-tags) from 2.22.2 to 2.22.3. - [Release notes](https://github.com/kpumuk/meta-tags/releases) - [Changelog](https://github.com/kpumuk/meta-tags/blob/main/CHANGELOG.md) - [Commits](https://github.com/kpumuk/meta-tags/compare/v2.22.2...v2.22.3) --- updated-dependencies: - dependency-name: meta-tags dependency-version: 2.22.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index 83ef51ca34e..132b7db2786 100644 --- a/Gemfile +++ b/Gemfile @@ -163,7 +163,7 @@ gem "matrix", "~> 0.4.3" gem "mcp", "~> 0.4.0" -gem "meta-tags", "~> 2.22.2" +gem "meta-tags", "~> 2.22.3" gem "paper_trail", "~> 17.0.0" diff --git a/Gemfile.lock b/Gemfile.lock index 8b9b6001d54..2ae54d7887e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -806,8 +806,8 @@ GEM json_rpc_handler (~> 0.1) messagebird-rest (5.0.0) jwt (< 4) - meta-tags (2.22.2) - actionpack (>= 6.0.0, < 8.2) + meta-tags (2.22.3) + actionpack (>= 6.0.0) method_source (1.1.0) mime-types (3.7.0) logger @@ -1626,7 +1626,7 @@ DEPENDENCIES matrix (~> 0.4.3) mcp (~> 0.4.0) md_to_pdf! - meta-tags (~> 2.22.2) + meta-tags (~> 2.22.3) mini_magick (~> 5.3.0) multi_json (~> 1.19.0) my_page! @@ -1979,7 +1979,7 @@ CHECKSUMS mcp (0.4.0) sha256=4d1dd2b99fbd81a5fdc808d258c38a4f57dd69751ee1e5f62b3ab40e31625a36 md_to_pdf (0.2.5) messagebird-rest (5.0.0) sha256=da4cc1efba3d5e4aa021fad07426c2cb6b326ce5670da5104bb8f6056a39d59c - meta-tags (2.22.2) sha256=7fe78af4a92be12091f473cb84a21f6bddbd37f24c4413172df76cd14fff9e83 + meta-tags (2.22.3) sha256=41ead5437140869717cbdd659cc6f1caa3e498b3e74b03ed63503b5b38ed504f method_source (1.1.0) sha256=181301c9c45b731b4769bc81e8860e72f9161ad7d66dd99103c9ab84f560f5c5 mime-types (3.7.0) sha256=dcebf61c246f08e15a4de34e386ebe8233791e868564a470c3fe77c00eed5e56 mime-types-data (3.2025.0924) sha256=f276bca15e59f35767cbcf2bc10e023e9200b30bd6a572c1daf7f4cc24994728 From ff4ecca3933cfc192f1f2c64a419bfe682981967 Mon Sep 17 00:00:00 2001 From: Christophe Bliard Date: Thu, 12 Feb 2026 10:14:14 +0100 Subject: [PATCH 280/293] Use postgres:16 in seed workflow to match runner's pg_dump version The ubuntu-latest runner has pg_dump 16 pre-installed, which fails against a PostgreSQL 17 server due to version mismatch. [skip ci] --- .github/workflows/seed-all-locales.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/seed-all-locales.yml b/.github/workflows/seed-all-locales.yml index 93ce07c08c4..a0a96cd3890 100644 --- a/.github/workflows/seed-all-locales.yml +++ b/.github/workflows/seed-all-locales.yml @@ -63,7 +63,7 @@ jobs: locale: ${{ fromJson(needs.prepare.outputs.locales) }} services: postgres: - image: postgres:17 + image: postgres:16 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres From 2bf1637c19503088a295b732c257eca0d9f0795c Mon Sep 17 00:00:00 2001 From: Christophe Bliard Date: Thu, 12 Feb 2026 10:17:09 +0100 Subject: [PATCH 281/293] Add tested ref to workflow summary in seed-all-locales [skip ci] --- .github/workflows/seed-all-locales.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/seed-all-locales.yml b/.github/workflows/seed-all-locales.yml index a0a96cd3890..64238bcc4a4 100644 --- a/.github/workflows/seed-all-locales.yml +++ b/.github/workflows/seed-all-locales.yml @@ -40,6 +40,9 @@ jobs: echo "ref=$BRANCH" >> "$GITHUB_OUTPUT" fi + - name: Print ref to summary + run: echo "Testing seeding on **${{ steps.use_input_or_find_latest_release.outputs.ref }}**" >> "$GITHUB_STEP_SUMMARY" + - name: Checkout uses: actions/checkout@v6 with: From 09cf416b28a399f27908ab95429490736dda3c0f Mon Sep 17 00:00:00 2001 From: Christophe Bliard Date: Thu, 12 Feb 2026 10:25:32 +0100 Subject: [PATCH 282/293] Silence SQL logs during seeding in seed-all-locales workflow [skip ci] --- .github/workflows/seed-all-locales.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/seed-all-locales.yml b/.github/workflows/seed-all-locales.yml index 64238bcc4a4..1711fd41369 100644 --- a/.github/workflows/seed-all-locales.yml +++ b/.github/workflows/seed-all-locales.yml @@ -102,4 +102,6 @@ jobs: EOF - name: Seed locale ${{ matrix.locale }} + env: + SILENCE_SQL_LOGS: 1 run: ruby script/i18n/test_seed_all_locales ${{ matrix.locale }} From 32197a8cfd8cf3a84e3bfffabf715c3fd33e4c4e Mon Sep 17 00:00:00 2001 From: Christophe Bliard Date: Thu, 12 Feb 2026 10:34:44 +0100 Subject: [PATCH 283/293] Test seeding with both standard and bim editions [skip ci] --- .github/workflows/seed-all-locales.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/seed-all-locales.yml b/.github/workflows/seed-all-locales.yml index 1711fd41369..78f299946aa 100644 --- a/.github/workflows/seed-all-locales.yml +++ b/.github/workflows/seed-all-locales.yml @@ -57,13 +57,14 @@ jobs: seed: needs: prepare if: github.repository == 'opf/openproject' - name: Seed ${{ matrix.locale }} + name: Seed in ${{ matrix.locale }} for ${{ matrix.edition }} runs-on: ubuntu-latest timeout-minutes: 15 strategy: fail-fast: false matrix: locale: ${{ fromJson(needs.prepare.outputs.locales) }} + edition: [standard, bim] services: postgres: image: postgres:16 @@ -101,7 +102,8 @@ jobs: url: <%= ENV["DATABASE_URL"] %> EOF - - name: Seed locale ${{ matrix.locale }} + - name: Seed in locale ${{ matrix.locale }} for edition ${{ matrix.edition }} env: SILENCE_SQL_LOGS: 1 + OPENPROJECT_EDITION: ${{ matrix.edition }} run: ruby script/i18n/test_seed_all_locales ${{ matrix.locale }} From c58ab88fec71ae0418f980590adba656ca904d3a Mon Sep 17 00:00:00 2001 From: Christophe Bliard Date: Thu, 12 Feb 2026 10:46:44 +0100 Subject: [PATCH 284/293] Use better job names in seeding workflow [ci skip] --- .github/workflows/seed-all-locales.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/seed-all-locales.yml b/.github/workflows/seed-all-locales.yml index 78f299946aa..82ccab8e68c 100644 --- a/.github/workflows/seed-all-locales.yml +++ b/.github/workflows/seed-all-locales.yml @@ -57,7 +57,7 @@ jobs: seed: needs: prepare if: github.repository == 'opf/openproject' - name: Seed in ${{ matrix.locale }} for ${{ matrix.edition }} + name: Seed in ${{ matrix.locale }} for ${{ matrix.edition }} edition runs-on: ubuntu-latest timeout-minutes: 15 strategy: @@ -102,7 +102,7 @@ jobs: url: <%= ENV["DATABASE_URL"] %> EOF - - name: Seed in locale ${{ matrix.locale }} for edition ${{ matrix.edition }} + - name: Seed in locale ${{ matrix.locale }} for ${{ matrix.edition }} edition env: SILENCE_SQL_LOGS: 1 OPENPROJECT_EDITION: ${{ matrix.edition }} From 0eecb87a9dc3f2ba30eb3d1ab28002ea3667b2d9 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Thu, 12 Feb 2026 16:24:59 +0100 Subject: [PATCH 285/293] Load Wiki Pages properly through the Wiki --- app/controllers/wiki_controller.rb | 2 +- app/controllers/wiki_menu_items_controller.rb | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index bd199274fef..72896441f84 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -370,7 +370,7 @@ class WikiController < ApplicationController end def find_wiki - @project = Project.find(params[:project_id]) + @project = Project.visible.find(params[:project_id]) @wiki = @project.wiki render_404 unless @wiki end diff --git a/app/controllers/wiki_menu_items_controller.rb b/app/controllers/wiki_menu_items_controller.rb index 5bd15ca8eac..85d5af1b287 100644 --- a/app/controllers/wiki_menu_items_controller.rb +++ b/app/controllers/wiki_menu_items_controller.rb @@ -37,7 +37,7 @@ class WikiMenuItemsController < ApplicationController next controller.wiki_menu_item.menu_identifier if controller.wiki_menu_item.try(:persisted?) project = controller.instance_variable_get(:@project) - if (page = WikiPage.find_by(wiki_id: project.wiki.id, slug: controller.params[:id])) + if (page = project.wiki.find_page(controller.params[:id])) default_menu_item(controller, page) end end @@ -45,7 +45,8 @@ class WikiMenuItemsController < ApplicationController current_menu_item :select_main_menu_item do |controller| next controller.wiki_menu_item.menu_identifier if controller.wiki_menu_item.try(:persisted?) - if (page = WikiPage.find_by(id: controller.params[:id])) + project = controller.instance_variable_get(:@project) + if (page = project.wiki.find_page(id: controller.params[:id])) default_menu_item(controller, page) end end @@ -114,7 +115,7 @@ class WikiMenuItemsController < ApplicationController end def select_main_menu_item - @page = WikiPage.find params[:id] + @page = @project.wiki.pages.find params[:id] @possible_wiki_pages = @project .wiki .pages @@ -127,9 +128,9 @@ class WikiMenuItemsController < ApplicationController end def replace_main_menu_item - current_page = WikiPage.find params[:id] + current_page = @project.wiki.find_page(params[:id]) - if (current_menu_item = current_page.menu_item) && (page = WikiPage.find_by(id: params[:wiki_page][:id])) && current_menu_item != page.menu_item + if (current_menu_item = current_page.menu_item) && (page = @project.wiki.find_page(params[:wiki_page][:id])) && current_menu_item != page.menu_item create_main_menu_item_for_wiki_page(page, current_menu_item.options) current_menu_item.destroy end From 7583481cfb355e1a17ebb50d5f3850e674c7d98b Mon Sep 17 00:00:00 2001 From: OpenProject Actions CI Date: Fri, 13 Feb 2026 03:52:45 +0000 Subject: [PATCH 286/293] update locales from crowdin [ci skip] --- config/locales/crowdin/de.yml | 2 +- config/locales/crowdin/ru.yml | 46 +++++++-------- config/locales/crowdin/zh-TW.yml | 24 ++++---- .../backlogs/config/locales/crowdin/de.yml | 18 +++--- .../backlogs/config/locales/crowdin/js-de.yml | 4 +- .../backlogs/config/locales/crowdin/js-ru.yml | 4 +- .../config/locales/crowdin/js-zh-TW.yml | 4 +- .../backlogs/config/locales/crowdin/ru.yml | 58 +++++++++---------- .../backlogs/config/locales/crowdin/zh-TW.yml | 58 +++++++++---------- modules/meeting/config/locales/crowdin/ru.yml | 4 +- .../meeting/config/locales/crowdin/zh-TW.yml | 4 +- 11 files changed, 113 insertions(+), 113 deletions(-) diff --git a/config/locales/crowdin/de.yml b/config/locales/crowdin/de.yml index 97dfd27a31d..65a0f442963 100644 --- a/config/locales/crowdin/de.yml +++ b/config/locales/crowdin/de.yml @@ -1157,7 +1157,7 @@ de: attributes: agile/sprint: sharing: "Sharing" - finish_date: "End date" + finish_date: "Enddatum" announcements: show_until: "Anzeigen bis" attachment: diff --git a/config/locales/crowdin/ru.yml b/config/locales/crowdin/ru.yml index 021bca0a901..989fd3a6dcd 100644 --- a/config/locales/crowdin/ru.yml +++ b/config/locales/crowdin/ru.yml @@ -111,21 +111,21 @@ ru: link: "вебхук" mcp_configurations: index: - description: "The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance." - resources_heading: "Resources" - resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." - resources_submit: "Update resources" - tools_heading: "Tools" - tools_description: "OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url)." - tools_submit: "Update tools" + description: "Протокол модели контекста позволяет агентам ИИ предоставлять своим пользователям инструменты и ресурсы, открытые данным экземпляром OpenProject." + resources_heading: "Ресурсы" + resources_description: "OpenProject реализует следующие ресурсы. Каждый из них может быть включен, переименован и описан по Вашему желанию. Для получения дополнительной информации обратитесь к [документации по ресурсам MCP](docs_url)." + resources_submit: "Обновить ресурсы" + tools_heading: "Инструменты" + tools_description: "OpenProject реализует следующие инструменты. Каждый из них может быть включен, переименован и описан по Вашему желанию. Для получения дополнительной информации обратитесь к [документации по инструментам MCP] (docs_url)." + tools_submit: "Обновить инструменты" multi_update: - success: "MCP configurations were updated successfully." + success: "Конфигурации MCP были успешно обновлены." server_form: - description_caption: "How the MCP server will be described to other applications who connect to it." - title_caption: "A short title shown to applications that connect to the MCP server." + description_caption: "Как сервер MCP будет описан для других приложений, которые подключатся к нему." + title_caption: "Краткое название, показываемое приложениям, которые подключаются к серверу MCP." update: - failure: "MCP configuration could not be updated." - success: "MCP configuration was updated successfully." + failure: "Конфигурация MCP не может быть обновлена." + success: "Конфигурация MCP была успешно обновлена." scim_clients: authentication_methods: sso: "JWT от поставщика идентификации" @@ -609,7 +609,7 @@ ru: is_for_all_blank_slate: heading: Для всех проектов description: Этот атрибут проекта включен во всех проектах, поскольку опция "Для всех проектов" отмечена. Его нельзя отключить для отдельных проектов. - enabled_via_assignee_when_submitted_html: This project attribute cannot be disabled since it is set as assignee when submitted for project initiation requests. + enabled_via_assignee_when_submitted_html: Этот атрибут проекта не может быть отключен, поскольку он установлен как назначаемый для запросов инициализации проекта. types: no_results_title_text: На данный момент доступных типов нет. form: @@ -1180,8 +1180,8 @@ ru: activerecord: attributes: agile/sprint: - sharing: "Sharing" - finish_date: "End date" + sharing: "Совместное использование" + finish_date: "Дата окончания" announcements: show_until: "Отобразить до" attachment: @@ -1556,7 +1556,7 @@ ru: not_available: "недоступно из-за конфигурации системы." not_deletable: "не может быть удален." not_current_user: "не является текущим пользователем." - only_one_active_sprint_allowed: "only one active sprint is allowed per project." + only_one_active_sprint_allowed: "для каждого проекта допускается только один активный спринт." not_found: "не найдено." not_a_date: "не является допустимой датой." not_a_datetime: "дата и время не являются допустимыми." @@ -2156,7 +2156,7 @@ ru: role: "Роль" roles: "Роли" search: "Поиск" - sprint: "Sprint" + sprint: "Спринт" start_date: "Дата начала" status: "Статус" state: "Область" @@ -2962,7 +2962,7 @@ ru: new_features_title: > Релиз содержит различные новые функции и улучшения, такие как: new_features_list: - line_0: Automated project initiation (Enterprise add-on). + line_0: Автоматическая инициация проекта. line_1: "Совещания: добавление новых или существующих пакетов работ в качестве итогов совещания." line_2: "Meetings: show iCal responses in OpenProject." line_3: "Повторяющиеся совещания: продублируйте пункты повестки дня на следующее совещание." @@ -4075,7 +4075,7 @@ ru: notice_successful_delete: "Удаление выполнено." notice_successful_cancel: "Отмена прошла успешно." notice_successful_update: "Обновление выполнено." - notice_successful_move: "Successful move from %{from} to %{to}." + notice_successful_move: "Успешное перемещение из %{from} в %{to}." notice_unsuccessful_create: "Создание не удалось." notice_unsuccessful_create_with_reason: "Создание не удалось: %{reason}" notice_unsuccessful_update: "Ошибка обновления." @@ -4239,7 +4239,7 @@ ru: permission_edit_project_query: "Редактирование запроса проекта" placeholders: default: "-" - templated_hint: Automatically generated through type %{type} + templated_hint: Автоматически сгенерировано через тип %{type} portfolio: count: zero: "0 портфолио" @@ -4422,9 +4422,9 @@ ru: setting_capture_external_links: "Перехват внешних ссылок" setting_capture_external_links_text: > Если эта функция включена, все внешние ссылки в форматированном тексте будут перенаправляться через страницу предупреждения, прежде чем покинуть приложение. Это помогает защитить пользователей от потенциально вредоносных внешних сайтов. - setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login: "Требуйте, чтобы пользователи входили в систему" setting_capture_external_links_require_login_text: > - When enabled, users wanting to click on external links need to be logged in before being able to continue. + Когда эта функция включена, пользователи, желающие перейти по внешним ссылкам, должны войти в систему, прежде чем смогут продолжить. setting_after_first_login_redirect_url: "Перенаправление первого входа" setting_after_first_login_redirect_url_text_html: > Задайте путь для перенаправления пользователей после их первого входа в систему. Если не задано, то пользователь будет перенаправлен на домашнюю страницу обзорного тура.
    Например: /my/page @@ -4738,7 +4738,7 @@ ru: project_mandate: "Мандат проекта" submission: description_template: > - **This work package was automatically created upon completion of the %{wizard_name} workflow.** A PDF artifact containing all submitted information has been generated and attached to this work package for reference and audit purposes. If you need to update or re-run the initiation steps, you can reopen the wizard at any time by using the link below: + **Этот рабочий пакет был автоматически создан по завершении рабочего процесса %{wizard_name} .** Артефакт в формате PDF, содержащий всю предоставленную информацию, был создан и прикреплен к этому рабочему пакету для справки и аудита. Если Вам необходимо обновить или повторно выполнить шаги инициации, Вы можете в любой момент снова открыть мастер, воспользовавшись приведенной ниже ссылкой: description: "Когда пользователь подает запрос на инициирование проекта, будет создан новый пакет работ с запросом, прикрепленным в виде PDF-файла. Настройки, приведенные ниже, определяют тип, статус и назначение этого нового рабочего пакета." work_package_type: "Тип пакета работ" work_package_type_caption: "Тип пакета работ, который должен использоваться для хранения завершенного артефакта." diff --git a/config/locales/crowdin/zh-TW.yml b/config/locales/crowdin/zh-TW.yml index 457f8332d6b..9cbbe93d339 100644 --- a/config/locales/crowdin/zh-TW.yml +++ b/config/locales/crowdin/zh-TW.yml @@ -113,7 +113,7 @@ zh-TW: index: description: "模型上下文協定允許 AI 代理向其使用者提供此 OpenProject 實體所揭露的工具與資源。" resources_heading: "資源" - resources_description: "OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url)." + resources_description: "OpenProject 實作下列資源。每個資源都可以依您的需求啟用、重新命名及描述。如需詳細資訊,請參閱 [關於 MCP 資源的文件](docs_url)。" resources_submit: "更新資源" tools_heading: "工具" tools_description: "OpenProject 實作下列工具。每個工具都可以依您的需求啟用、重新命名和描述。如需詳細資訊,請參閱 [MCP 工具說明文件](docs_url)。" @@ -122,7 +122,7 @@ zh-TW: success: "MCP 組態已成功更新。" server_form: description_caption: "MCP 伺服器將如何描述給連線到它的其他應用程式。" - title_caption: "A short title shown to applications that connect to the MCP server." + title_caption: "顯示給與 MCP 伺服器連線的應用程式的簡短標題。" update: failure: "無法更新 MCP 設定。" success: "MCP 組態已成功更新。" @@ -622,8 +622,8 @@ zh-TW: new_label: "新優先權" creation_wizard: errors: - no_work_package_type: "Failed to enable project initiation request because it requires at least one active work package type and this project has none. Please add at least one work package type to this project." - no_status_when_submitted: "Failed to enable project initiation request because work package type %{type} requires at least one status associated with it. Please enable at least one status workflow for this work package type." + no_work_package_type: "啟用專案啟動請求失敗,因為它需要至少一個作用中的工作套件類型,而此專案沒有。請為此專案新增至少一個工作套件類型。" + no_status_when_submitted: "啟用專案啟動請求失敗,因為工作包類型 %{type} 至少需要一個狀態與之相關。請為此工作包類型啟用至少一個狀態工作流程。" export: description_attachment_export: "產生的產物將會以 PDF 附件的形式,儲存到該產物的工作套件中。" description_file_link_export: "該成品工作套件將會包含一個檔案連結,指向儲存在外部檔案儲存空間中的 PDF 檔案。這要求有一個具備檔案儲存功能的空間,並且其中有為此專案自動化管理的專案資料夾。目前僅支援 Nextcloud 檔案儲存空間。" @@ -1151,8 +1151,8 @@ zh-TW: activerecord: attributes: agile/sprint: - sharing: "Sharing" - finish_date: "End date" + sharing: "分享" + finish_date: "結束日期" announcements: show_until: "只顯示到" attachment: @@ -1527,7 +1527,7 @@ zh-TW: not_available: "由於系統配置所以不可用" not_deletable: "無法刪除" not_current_user: "不是目前使用者。" - only_one_active_sprint_allowed: "only one active sprint is allowed per project." + only_one_active_sprint_allowed: "每個專案只允許一個活動衝刺。" not_found: "未找到" not_a_date: "不是有效的日期。" not_a_datetime: "不是有效的日期時間。" @@ -2070,7 +2070,7 @@ zh-TW: role: "角色" roles: "角色" search: "搜尋" - sprint: "Sprint" + sprint: "衝刺" start_date: "起始日期" status: "狀態" state: "狀態" @@ -3923,7 +3923,7 @@ zh-TW: notice_successful_delete: "刪除成功" notice_successful_cancel: "取消成功" notice_successful_update: "更新成功" - notice_successful_move: "Successful move from %{from} to %{to}." + notice_successful_move: "成功從 %{from} 移至 %{to}." notice_unsuccessful_create: "建立失敗。" notice_unsuccessful_create_with_reason: "建立失敗:%{reason}" notice_unsuccessful_update: "更新失敗。" @@ -4083,7 +4083,7 @@ zh-TW: permission_edit_project_query: "編輯專案查詢" placeholders: default: "-" - templated_hint: Automatically generated through type %{type} + templated_hint: 透過類型 %{type}自動產生 portfolio: count: zero: "0 個組合" @@ -4268,9 +4268,9 @@ zh-TW: setting_capture_external_links: "擷取外部連結" setting_capture_external_links_text: > 啟用後,格式化文字中的所有外部連結都會在離開應用程式前透過警告頁重定向。這有助於保護使用者遠離潛在的惡意外部網站。 - setting_capture_external_links_require_login: "Require users to be logged in" + setting_capture_external_links_require_login: "要求使用者登入" setting_capture_external_links_require_login_text: > - When enabled, users wanting to click on external links need to be logged in before being able to continue. + 啟用後,想要按一下外部連結的使用者必須先登入才能繼續。 setting_after_first_login_redirect_url: "首次登入重新導向" setting_after_first_login_redirect_url_text_html: > 設定使用者首次登入後的重新導向路徑。如果為空,則會重定向到上線導覽的首頁。
    範例:/my/page diff --git a/modules/backlogs/config/locales/crowdin/de.yml b/modules/backlogs/config/locales/crowdin/de.yml index 7f650a56f84..3c32a0fdd8d 100644 --- a/modules/backlogs/config/locales/crowdin/de.yml +++ b/modules/backlogs/config/locales/crowdin/de.yml @@ -26,7 +26,7 @@ de: activerecord: attributes: sprint: - duration: "Sprint duration" + duration: "Sprintdauer" work_package: position: "Position" story_points: "Story-Punkte" @@ -46,7 +46,7 @@ de: task_type: "Aufgaben-Typ" backlogs: any: "beliebig" - column_width: "Column width" + column_width: "Spaltenbreite" definition_of_done: "Definition of Done" impediment: "Hindernis" label_versions_default_fold_state: "Versionen eingeklappt anzeigen" @@ -54,7 +54,7 @@ de: work_package_is_closed: "Arbeitspaket ist abgeschlossen, wenn" label_is_done_status: "Status %{status_name} bedeutet abgeschlossen" points_label: - one: "point" + one: "Punkt" other: "points" positions_could_not_be_rebuilt: "Positionen konnten nicht neu berechnet werden." positions_rebuilt_successfully: "Positionen wurden neu berechnet." @@ -73,10 +73,10 @@ de: header_backlogs: "Backlog-Modul" button_update_backlogs: "Backlog-Modul aktualisieren" backlog_component: - blankslate_title: "%{name} is empty" + blankslate_title: "%{name} ist leer" blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: - label_toggle_backlog: "Collapse/Expand %{name}" + label_toggle_backlog: "%{name} ein-/ausklappen" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" @@ -84,15 +84,15 @@ de: backlog_menu_component: label_actions: "Backlog actions" action_menu: - edit_sprint: "Edit sprint" + edit_sprint: "Sprint bearbeiten" new_story: "New story" stories_tasks: "Stories/Tasks" task_board: "Task board" burndown_chart: "Burndown chart" wiki: "Wiki" - properties: "Properties" + properties: "Eigenschaften" story_component: - label_drag_story: "Move %{name}" + label_drag_story: "%{name} verschieben" story_menu_component: label_actions: "Story actions" backlogs_points_burn_direction: "Burnup/-down Punkte" @@ -102,7 +102,7 @@ de: backlogs_task: "Aufgabe" backlogs_task_type: "Aufgaben-Typ" backlogs_wiki_template: "Vorlage für das Sprint-Wiki" - backlogs_empty_title: "No versions are defined yet" + backlogs_empty_title: "Es sind noch keine Versionen definiert" backlogs_empty_action_text: "To start using backlogs, please create a version first" backlogs_not_configured_title: "Backlogs not configured" backlogs_not_configured_description: "Story and task types need to be set before using this module." diff --git a/modules/backlogs/config/locales/crowdin/js-de.yml b/modules/backlogs/config/locales/crowdin/js-de.yml index 55df96298af..3a825d8f953 100644 --- a/modules/backlogs/config/locales/crowdin/js-de.yml +++ b/modules/backlogs/config/locales/crowdin/js-de.yml @@ -25,5 +25,5 @@ de: properties: storyPoints: "Story Punkte" burndown: - day: "Day" - points: "Points" + day: "Tag" + points: "Punkte" diff --git a/modules/backlogs/config/locales/crowdin/js-ru.yml b/modules/backlogs/config/locales/crowdin/js-ru.yml index 4f7e851860a..2533ffb1d4d 100644 --- a/modules/backlogs/config/locales/crowdin/js-ru.yml +++ b/modules/backlogs/config/locales/crowdin/js-ru.yml @@ -25,5 +25,5 @@ ru: properties: storyPoints: "Исторические точки" burndown: - day: "Day" - points: "Points" + day: "День" + points: "Точки" diff --git a/modules/backlogs/config/locales/crowdin/js-zh-TW.yml b/modules/backlogs/config/locales/crowdin/js-zh-TW.yml index 54e76923bed..bdf3466bfcc 100644 --- a/modules/backlogs/config/locales/crowdin/js-zh-TW.yml +++ b/modules/backlogs/config/locales/crowdin/js-zh-TW.yml @@ -25,5 +25,5 @@ zh-TW: properties: storyPoints: "故事點數" burndown: - day: "Day" - points: "Points" + day: "日" + points: "燃盡點" diff --git a/modules/backlogs/config/locales/crowdin/ru.yml b/modules/backlogs/config/locales/crowdin/ru.yml index 45dc34f3534..87b53de1666 100644 --- a/modules/backlogs/config/locales/crowdin/ru.yml +++ b/modules/backlogs/config/locales/crowdin/ru.yml @@ -26,7 +26,7 @@ ru: activerecord: attributes: sprint: - duration: "Sprint duration" + duration: "Продолжительность спринта" work_package: position: "Позиция" story_points: "Стори поинты" @@ -46,7 +46,7 @@ ru: task_type: "Тип задачи" backlogs: any: "любой" - column_width: "Column width" + column_width: "Ширина столбца" definition_of_done: "Определение термина \"Завершено\"" impediment: "Препятствие" label_versions_default_fold_state: "Показать свернутые версии" @@ -57,7 +57,7 @@ ru: one: "point" few: "points" many: "points" - other: "points" + other: "точки" positions_could_not_be_rebuilt: "Не удалось восстановить позиции." positions_rebuilt_successfully: "Позиции успешно восстановлены." rebuild: "Восстановить" @@ -68,8 +68,8 @@ ru: story_points: one: "%{count} story point" few: "%{count} story points" - many: "%{count} story points" - other: "%{count} story points" + many: "%{count} сюжетные точки" + other: "%{count} сюжетные точки" task: "Задача" task_color: "Цвет задачи" unassigned: "Нераспределенные" @@ -77,28 +77,28 @@ ru: header_backlogs: "Модуль бэклогов" button_update_backlogs: "Обновить модуль бэклогов" backlog_component: - blankslate_title: "%{name} is empty" - blankslate_description: "No items planned yet. Drag items here to add them." + blankslate_title: "%{name} пустой" + blankslate_description: "Пока ничего не запланировано. Перетащите элементы сюда, чтобы добавить их." backlog_header_component: - label_toggle_backlog: "Collapse/Expand %{name}" + label_toggle_backlog: "Свернуть/Развернуть %{name}" label_story_count: - zero: "No stories in backlog" - one: "%{count} story in backlog" - other: "%{count} stories in backlog" + zero: "Нет историй в бэклоге" + one: "%{count} история в бэклоге" + other: "%{count} историй в бэклоге" backlog_menu_component: - label_actions: "Backlog actions" + label_actions: "Действия в рамках бэклога" action_menu: - edit_sprint: "Edit sprint" - new_story: "New story" - stories_tasks: "Stories/Tasks" - task_board: "Task board" - burndown_chart: "Burndown chart" + edit_sprint: "Редактировать спринт" + new_story: "Новая история" + stories_tasks: "Истории/Задачи" + task_board: "Панель задач" + burndown_chart: "Сводная таблица" wiki: "Wiki" - properties: "Properties" + properties: "Свойства" story_component: - label_drag_story: "Move %{name}" + label_drag_story: "Переместить %{name}" story_menu_component: - label_actions: "Story actions" + label_actions: "Действия истории" backlogs_points_burn_direction: "Точки выгорания вверх/вниз" backlogs_product_backlog: "Требования к продукту, с приоритетами" backlogs_story: "История" @@ -106,14 +106,14 @@ ru: backlogs_task: "Задача" backlogs_task_type: "Тип задачи" backlogs_wiki_template: "Шаблон для wiki-страницы по спринту" - backlogs_empty_title: "No versions are defined yet" - backlogs_empty_action_text: "To start using backlogs, please create a version first" - backlogs_not_configured_title: "Backlogs not configured" - backlogs_not_configured_description: "Story and task types need to be set before using this module." - backlogs_not_configured_action_text: "Configure Backlogs" + backlogs_empty_title: "Версии пока не определены" + backlogs_empty_action_text: "Чтобы начать использовать бэклоги, сначала создайте версию" + backlogs_not_configured_title: "Бэклоги не настроены" + backlogs_not_configured_description: "Перед использованием этого модуля необходимо задать типы историй и задач." + backlogs_not_configured_action_text: "Настройка бэклогов" burndown: - story_points: "Story points" - story_points_ideal: "Story points (ideal)" + story_points: "Точки истории" + story_points_ideal: "Точки истории (идеально)" errors: attributes: task_type: @@ -133,8 +133,8 @@ ru: project_module_backlogs: "Бэклоги" rb_burndown_charts: show: - blankslate_title: "No burndown data available" - blankslate_description: "Set start and end date for the sprint to generate a burndown chart." + blankslate_title: "Нет сводных данных" + blankslate_description: "Установите дату начала и окончания спринта, чтобы сгенерировать сводную таблицу." remaining_hours: "оставшиеся часы" version_settings_display_label: "Колонка в бэклоге" version_settings_display_option_left: "влево" diff --git a/modules/backlogs/config/locales/crowdin/zh-TW.yml b/modules/backlogs/config/locales/crowdin/zh-TW.yml index 36e5af479ba..1e9624c8690 100644 --- a/modules/backlogs/config/locales/crowdin/zh-TW.yml +++ b/modules/backlogs/config/locales/crowdin/zh-TW.yml @@ -26,7 +26,7 @@ zh-TW: activerecord: attributes: sprint: - duration: "Sprint duration" + duration: "衝刺時間" work_package: position: "位置" story_points: "需求重要性" @@ -46,7 +46,7 @@ zh-TW: task_type: "任務類型" backlogs: any: "任何" - column_width: "Column width" + column_width: "欄寬" definition_of_done: "定義" impediment: "阻礙" label_versions_default_fold_state: "顯示精簡版本" @@ -54,7 +54,7 @@ zh-TW: work_package_is_closed: "視為工作套件已完成" label_is_done_status: "狀態 %{status_name} 表示完成" points_label: - other: "points" + other: "點" positions_could_not_be_rebuilt: "無法重建位置" positions_rebuilt_successfully: "位置重建成功" rebuild: "重建" @@ -63,7 +63,7 @@ zh-TW: show_burndown_chart: "未完成圖" story: "使用者需求" story_points: - other: "%{count} story points" + other: "%{count} 故事點" task: "任務" task_color: "任務顏色" unassigned: "尚未指派" @@ -71,28 +71,28 @@ zh-TW: header_backlogs: "待辦清單模組" button_update_backlogs: "更新待辦清單模組" backlog_component: - blankslate_title: "%{name} is empty" - blankslate_description: "No items planned yet. Drag items here to add them." + blankslate_title: "%{name} 為空" + blankslate_description: "尚未規劃任何項目。將項目拖曳至此即可新增。" backlog_header_component: - label_toggle_backlog: "Collapse/Expand %{name}" + label_toggle_backlog: "折疊/展開 %{name}" label_story_count: - zero: "No stories in backlog" - one: "%{count} story in backlog" - other: "%{count} stories in backlog" + zero: "目前沒有待辦故事" + one: "待辦清單中有 %{count} 個故事" + other: "待辦清單中有 %{count} 個故事" backlog_menu_component: - label_actions: "Backlog actions" + label_actions: "待辦清單操作" action_menu: - edit_sprint: "Edit sprint" - new_story: "New story" - stories_tasks: "Stories/Tasks" - task_board: "Task board" - burndown_chart: "Burndown chart" - wiki: "Wiki" - properties: "Properties" + edit_sprint: "編輯衝刺" + new_story: "新增故事" + stories_tasks: "故事/任務" + task_board: "任務板" + burndown_chart: "燃盡圖" + wiki: "維基" + properties: "屬性" story_component: - label_drag_story: "Move %{name}" + label_drag_story: "移動 %{name}" story_menu_component: - label_actions: "Story actions" + label_actions: "故事相關操作" backlogs_points_burn_direction: "重要性 增加/減少" backlogs_product_backlog: "產品待辦事項" backlogs_story: "使用者需求" @@ -100,14 +100,14 @@ zh-TW: backlogs_task: "任務" backlogs_task_type: "任務類型" backlogs_wiki_template: "進度的 Wiki 頁面樣板" - backlogs_empty_title: "No versions are defined yet" - backlogs_empty_action_text: "To start using backlogs, please create a version first" - backlogs_not_configured_title: "Backlogs not configured" - backlogs_not_configured_description: "Story and task types need to be set before using this module." - backlogs_not_configured_action_text: "Configure Backlogs" + backlogs_empty_title: "尚未定義版本" + backlogs_empty_action_text: "若要啟用待辦清單功能,請先建立一個版本。" + backlogs_not_configured_title: "尚未設定待辦清單。" + backlogs_not_configured_description: "使用此模組前,需先設定故事和任務類型。" + backlogs_not_configured_action_text: "設定 Backlogs" burndown: - story_points: "Story points" - story_points_ideal: "Story points (ideal)" + story_points: "故事點" + story_points_ideal: "理想估算故事點數" errors: attributes: task_type: @@ -127,8 +127,8 @@ zh-TW: project_module_backlogs: "待辦事項" rb_burndown_charts: show: - blankslate_title: "No burndown data available" - blankslate_description: "Set start and end date for the sprint to generate a burndown chart." + blankslate_title: "沒有可用的燃盡圖資料" + blankslate_description: "設定衝刺的開始和結束日期,以產生燃盡圖表。" remaining_hours: "剩餘工時" version_settings_display_label: "待辦事項的欄位" version_settings_display_option_left: "左" diff --git a/modules/meeting/config/locales/crowdin/ru.yml b/modules/meeting/config/locales/crowdin/ru.yml index 9ae9af814ab..b1b358feaf4 100644 --- a/modules/meeting/config/locales/crowdin/ru.yml +++ b/modules/meeting/config/locales/crowdin/ru.yml @@ -68,9 +68,9 @@ ru: errors: models: meeting_participant: - user_invalid: "is not a valid participant." + user_invalid: "не является действительным участником." meeting_agenda_item: - user_invalid: "is not a valid participant." + user_invalid: "не является действительным участником." recurring_meeting: must_cover_existing_meetings: one: "В серии есть одно открытое совещание, которое не включено в новый график. Измените график, чтобы включить все существующие совещания." diff --git a/modules/meeting/config/locales/crowdin/zh-TW.yml b/modules/meeting/config/locales/crowdin/zh-TW.yml index 7520cef732d..3ffd3d2a71f 100644 --- a/modules/meeting/config/locales/crowdin/zh-TW.yml +++ b/modules/meeting/config/locales/crowdin/zh-TW.yml @@ -65,9 +65,9 @@ zh-TW: errors: models: meeting_participant: - user_invalid: "is not a valid participant." + user_invalid: "不是有效的參與者。" meeting_agenda_item: - user_invalid: "is not a valid participant." + user_invalid: "不是有效的參與者。" recurring_meeting: must_cover_existing_meetings: one: "該系列中有一個公開會議未包含在新的時間表中。調整時間表以包含所有現有會議。" From 136ed3ee23d46be087d0e4f12571ba419c01219f Mon Sep 17 00:00:00 2001 From: Christophe Bliard Date: Thu, 12 Feb 2026 10:53:20 +0100 Subject: [PATCH 287/293] Add commit SHA with link to workflow summary [skip ci] --- .github/workflows/seed-all-locales.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/seed-all-locales.yml b/.github/workflows/seed-all-locales.yml index 82ccab8e68c..d2c9d389e90 100644 --- a/.github/workflows/seed-all-locales.yml +++ b/.github/workflows/seed-all-locales.yml @@ -40,14 +40,17 @@ jobs: echo "ref=$BRANCH" >> "$GITHUB_OUTPUT" fi - - name: Print ref to summary - run: echo "Testing seeding on **${{ steps.use_input_or_find_latest_release.outputs.ref }}**" >> "$GITHUB_STEP_SUMMARY" - - name: Checkout uses: actions/checkout@v6 with: ref: ${{ steps.use_input_or_find_latest_release.outputs.ref }} + - name: Print ref to summary + run: | + SHA=$(git rev-parse HEAD) + SHORT_SHA=$(git rev-parse --short HEAD) + echo "Testing seeding on **${{ steps.use_input_or_find_latest_release.outputs.ref }}** ([$SHORT_SHA](https://github.com/opf/openproject/commit/$SHA))" >> "$GITHUB_STEP_SUMMARY" + - name: List available locales id: list run: | From d61bb47b2bc83f3381e12f3f18fb60963d527200 Mon Sep 17 00:00:00 2001 From: Jan Sandbrink Date: Thu, 12 Feb 2026 09:10:40 +0100 Subject: [PATCH 288/293] Rename rest_api_enabled to api_tokens_enabled The name of this setting was pretty outdated by now. It might have disabled the entire API in the past, but that time is long gone. By now the APIv3 can't be disabled at all and OpenProject would fall apart if it was disabled. The only thing that this setting changes, is whether users can create an access token in their account settings and whether tokens created this way are accepted by OpenProject. So naming and description have been adapted accordingly. --- .../api_tokens_section_component.rb | 2 +- .../concerns/accounts/current_user.rb | 2 +- .../my/access_tokens_controller.rb | 2 +- app/forms/admin/settings/api_settings_form.rb | 2 +- app/models/user.rb | 2 +- config/constants/settings/definition.rb | 10 +++-- config/locales/en.yml | 5 ++- ...2133700_rename_setting_rest_api_enabled.rb | 41 +++++++++++++++++++ .../api-and-webhooks/README.md | 5 ++- .../strategies/warden/user_api_token.rb | 4 +- .../strategies/warden/user_basic_auth.rb | 2 +- spec/constants/settings/definition_spec.rb | 4 +- spec/features/users/my/access_tokens_spec.rb | 4 +- .../admin/settings/api_settings_form_spec.rb | 4 +- 14 files changed, 70 insertions(+), 19 deletions(-) create mode 100644 db/migrate/20260212133700_rename_setting_rest_api_enabled.rb diff --git a/app/components/my/access_token/api_tokens_section_component.rb b/app/components/my/access_token/api_tokens_section_component.rb index 8c2625a9415..932141cf02b 100644 --- a/app/components/my/access_token/api_tokens_section_component.rb +++ b/app/components/my/access_token/api_tokens_section_component.rb @@ -56,7 +56,7 @@ module My def token_available? case token_type.to_s - when "Token::API" then Setting.rest_api_enabled? + when "Token::API" then Setting.api_tokens_enabled? when "Token::ICalMeeting" then Setting.ical_enabled? when "Token::RSS" then Setting.feeds_enabled? else raise ArgumentError, "Unknown token type: #{token_type}" diff --git a/app/controllers/concerns/accounts/current_user.rb b/app/controllers/concerns/accounts/current_user.rb index bc089498e8c..7b0d14d54bf 100644 --- a/app/controllers/concerns/accounts/current_user.rb +++ b/app/controllers/concerns/accounts/current_user.rb @@ -101,7 +101,7 @@ module Accounts::CurrentUser end def current_api_key_user - return unless Setting.rest_api_enabled? && api_request? + return unless Setting.api_tokens_enabled? && api_request? key = api_key_from_request diff --git a/app/controllers/my/access_tokens_controller.rb b/app/controllers/my/access_tokens_controller.rb index 65a32670f4f..250eabb8f72 100644 --- a/app/controllers/my/access_tokens_controller.rb +++ b/app/controllers/my/access_tokens_controller.rb @@ -172,7 +172,7 @@ module My helper_method :has_tokens? def has_tokens? - Setting.feeds_enabled? || Setting.rest_api_enabled? || current_user.ical_tokens.any? + Setting.feeds_enabled? || Setting.api_tokens_enabled? || current_user.ical_tokens.any? end def set_api_token diff --git a/app/forms/admin/settings/api_settings_form.rb b/app/forms/admin/settings/api_settings_form.rb index dbecf781b0b..d90e409fa55 100644 --- a/app/forms/admin/settings/api_settings_form.rb +++ b/app/forms/admin/settings/api_settings_form.rb @@ -48,7 +48,7 @@ module Admin end settings_form do |sf| - sf.check_box(name: :rest_api_enabled) + sf.check_box(name: :api_tokens_enabled, caption: I18n.t(:setting_api_tokens_enabled_caption)) sf.text_field( name: :apiv3_max_page_size, diff --git a/app/models/user.rb b/app/models/user.rb index b8ca21b3aad..0a7fd69bbc9 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -448,7 +448,7 @@ class User < Principal end def self.find_by_api_key(key) - return nil unless Setting.rest_api_enabled? + return nil unless Setting.api_tokens_enabled? token = Token::API.find_by_plaintext_value(key) diff --git a/config/constants/settings/definition.rb b/config/constants/settings/definition.rb index f5f0bb805f5..cfd2af505b6 100644 --- a/config/constants/settings/definition.rb +++ b/config/constants/settings/definition.rb @@ -126,6 +126,13 @@ module Settings default: :quarantine, allowed: %i[quarantine delete] }, + api_tokens_enabled: { + default: true, + description: "Decide whether users can create personal API tokens in their account settings", + # Keeping old name only for backwards-compatibility, can be removed in OpenProject 18.0 + env_alias: "OPENPROJECT_REST__API__ENABLED", + format: :boolean + }, auth_source_sso: { description: "Configuration for Header-based Single Sign-On", format: :hash, @@ -969,9 +976,6 @@ module Settings repository_truncate_at: { default: 500 }, - rest_api_enabled: { - default: true - }, scm: { format: :hash, default: {}, diff --git a/config/locales/en.yml b/config/locales/en.yml index bad09852a01..24696a3adf2 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -4571,6 +4571,10 @@ en: setting_smtp_password: "SMTP password" setting_smtp_domain: "SMTP HELO domain" setting_activity_days_default: "Days displayed on project activity" + setting_api_tokens_enabled: "Enable API tokens" + setting_api_tokens_enabled_caption: > + Decide whether users can create personal API tokens in their account settings. These tokens can be used to access the different + APIs of OpenProject, such as APIv3 and MCP. setting_app_subtitle: "Application subtitle" setting_app_title: "Application title" setting_attachment_max_size: "Attachment max. size" @@ -4685,7 +4689,6 @@ en: setting_repository_checkout_text: "Checkout instruction text" setting_repository_log_display_limit: "Maximum number of revisions displayed on file log" setting_repository_truncate_at: "Maximum number of files displayed in the repository browser" - setting_rest_api_enabled: "Enable REST web service" setting_self_registration: "Self-registration" setting_self_registration_caption: > Choose the self-registration mechanism for users. Be careful with the setting you choose, as some diff --git a/db/migrate/20260212133700_rename_setting_rest_api_enabled.rb b/db/migrate/20260212133700_rename_setting_rest_api_enabled.rb new file mode 100644 index 00000000000..914068abf2b --- /dev/null +++ b/db/migrate/20260212133700_rename_setting_rest_api_enabled.rb @@ -0,0 +1,41 @@ +# 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_relative "migration_utils/setting_renamer" + +class RenameSettingRestAPIEnabled < ActiveRecord::Migration[8.0] + def up + ::Migration::MigrationUtils::SettingRenamer.rename(:rest_api_enabled, :api_tokens_enabled) + end + + def down + ::Migration::MigrationUtils::SettingRenamer.rename(:api_tokens_enabled, :rest_api_enabled) + end +end diff --git a/docs/system-admin-guide/api-and-webhooks/README.md b/docs/system-admin-guide/api-and-webhooks/README.md index 74df21f2dcf..e4c3d9ab0c7 100644 --- a/docs/system-admin-guide/api-and-webhooks/README.md +++ b/docs/system-admin-guide/api-and-webhooks/README.md @@ -13,9 +13,12 @@ Navigate to **Administration → API and webhooks**. ## API + ![API settings in OpenProject administration](openproject_system_admin_guide_api.png) -Here, you can manage the **REST web service** to selectively control whether foreign applications may access your OpenProject API endpoints from within the browser. This setting allows users to access the OpenProject API using an API token created from the users "Account settings" page. You can set the **maximum page size** the API will respond with. It will not be possible to perform API requests that return more values on a single page. You can also enable **write access to read-only attributes**, which will allow administrators to write static read-only attributes during creation, such as *createdAt* and *author*. +Here, you can manage whether users can create personal API tokens, this setting allows users to access the OpenProject APIs using an API token created from the user's "Account settings" page. +You can set the **maximum page size** the API will respond with. It will not be possible to perform API requests that return more values on a single page. +You can also enable **write access to read-only attributes**, which will allow administrators to write static read-only attributes during creation, such as *createdAt* and *author*. This can be useful during data imports. ### Documentation diff --git a/lib_static/open_project/authentication/strategies/warden/user_api_token.rb b/lib_static/open_project/authentication/strategies/warden/user_api_token.rb index ccfabca77bb..475982bdcc2 100644 --- a/lib_static/open_project/authentication/strategies/warden/user_api_token.rb +++ b/lib_static/open_project/authentication/strategies/warden/user_api_token.rb @@ -35,12 +35,12 @@ module OpenProject ## # Allows users to authenticate using their API key as a Bearer token. # Note that in order for a user to be able to generate one - # `Setting.rest_api_enabled` has to be `1`. + # `Setting.api_tokens_enabled` has to be `1`. class UserAPIToken < ::Warden::Strategies::Base include FailWithHeader def valid? - return false unless Setting.rest_api_enabled? + return false unless Setting.api_tokens_enabled? @access_token = ::Doorkeeper::OAuth::Token.from_bearer_authorization( ::Doorkeeper::Grape::AuthorizationDecorator.new(request) diff --git a/lib_static/open_project/authentication/strategies/warden/user_basic_auth.rb b/lib_static/open_project/authentication/strategies/warden/user_basic_auth.rb index bc211177042..9bbb317b2a7 100644 --- a/lib_static/open_project/authentication/strategies/warden/user_basic_auth.rb +++ b/lib_static/open_project/authentication/strategies/warden/user_basic_auth.rb @@ -37,7 +37,7 @@ module OpenProject ## # Allows users to authenticate using their API key via basic auth. # Note that in order for a user to be able to generate one - # `Setting.rest_api_enabled` has to be `1`. + # `Setting.api_tokens_enabled` has to be `true`. # # The basic auth credentials are expected to contain the literal 'apikey' # as the user name and the API key as the password. diff --git a/spec/constants/settings/definition_spec.rb b/spec/constants/settings/definition_spec.rb index f573305b025..9281def81fa 100644 --- a/spec/constants/settings/definition_spec.rb +++ b/spec/constants/settings/definition_spec.rb @@ -165,8 +165,8 @@ RSpec.describe Settings::Definition, :settings_reset do it "overriding boolean configuration from ENV will cast the value", with_env: { "OPENPROJECT_REST__API__ENABLED" => "0" } do - reset(:rest_api_enabled) - expect(all[:rest_api_enabled].value).to be false + reset(:api_tokens_enabled) + expect(all[:api_tokens_enabled].value).to be false end it "overriding symbol configuration having allowed values from ENV will cast the value before validation check", diff --git a/spec/features/users/my/access_tokens_spec.rb b/spec/features/users/my/access_tokens_spec.rb index a3070f9e6cd..7279782d346 100644 --- a/spec/features/users/my/access_tokens_spec.rb +++ b/spec/features/users/my/access_tokens_spec.rb @@ -46,7 +46,7 @@ RSpec.describe "my access tokens", :js do end describe "API tokens" do - context "when API access is disabled via global settings", with_settings: { rest_api_enabled: false } do + context "when API tokens are disabled via global setting", with_settings: { api_tokens_enabled: false } do it "shows notice about disabled token" do visit my_access_tokens_path @@ -57,7 +57,7 @@ RSpec.describe "my access tokens", :js do end end - context "when API access is enabled via global settings", with_settings: { rest_api_enabled: true } do + context "when API tokens are enabled via global setting", with_settings: { api_tokens_enabled: true } do it "API tokens can be generated and revoked" do visit my_access_tokens_path diff --git a/spec/forms/admin/settings/api_settings_form_spec.rb b/spec/forms/admin/settings/api_settings_form_spec.rb index 29952b0991a..83c5ac262d8 100644 --- a/spec/forms/admin/settings/api_settings_form_spec.rb +++ b/spec/forms/admin/settings/api_settings_form_spec.rb @@ -41,8 +41,8 @@ RSpec.describe Admin::Settings::APISettingsForm, type: :forms do end it "renders", :aggregate_failures do - expect(rendered_form).to have_field "Enable REST web service", type: :checkbox do |field| - expect(field["name"]).to eq "settings[rest_api_enabled]" + expect(rendered_form).to have_field "Enable API tokens", type: :checkbox do |field| + expect(field["name"]).to eq "settings[api_tokens_enabled]" end expect(rendered_form).to have_field "Maximum API page size", type: :number do |field| From 3af9130e4c0edc22b2b0c2ba176ffb08ca04620b Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Fri, 13 Feb 2026 09:59:03 +0100 Subject: [PATCH 289/293] Fix specs and rubocop for wiki page menu items --- app/controllers/wiki_menu_items_controller.rb | 24 +++++++++++-------- .../wiki_menu_items_controller_spec.rb | 4 ++-- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/app/controllers/wiki_menu_items_controller.rb b/app/controllers/wiki_menu_items_controller.rb index 85d5af1b287..104f857d0ef 100644 --- a/app/controllers/wiki_menu_items_controller.rb +++ b/app/controllers/wiki_menu_items_controller.rb @@ -37,7 +37,7 @@ class WikiMenuItemsController < ApplicationController next controller.wiki_menu_item.menu_identifier if controller.wiki_menu_item.try(:persisted?) project = controller.instance_variable_get(:@project) - if (page = project.wiki.find_page(controller.params[:id])) + if (page = project.wiki.pages.find_by(id: controller.params[:id])) default_menu_item(controller, page) end end @@ -46,7 +46,7 @@ class WikiMenuItemsController < ApplicationController next controller.wiki_menu_item.menu_identifier if controller.wiki_menu_item.try(:persisted?) project = controller.instance_variable_get(:@project) - if (page = project.wiki.find_page(id: controller.params[:id])) + if (page = project.wiki.pages.find_by(id: controller.params[:id])) default_menu_item(controller, page) end end @@ -127,12 +127,16 @@ class WikiMenuItemsController < ApplicationController end end - def replace_main_menu_item - current_page = @project.wiki.find_page(params[:id]) + def replace_main_menu_item # rubocop:disable Metrics/AbcSize + current_page = @project.wiki.pages.find(params[:id]) - if (current_menu_item = current_page.menu_item) && (page = @project.wiki.find_page(params[:wiki_page][:id])) && current_menu_item != page.menu_item - create_main_menu_item_for_wiki_page(page, current_menu_item.options) - current_menu_item.destroy + if current_menu_item = current_page.menu_item + page = @project.wiki.pages.find(params[:wiki_page][:id]) + + if page && current_menu_item != page.menu_item + create_main_menu_item_for_wiki_page(page, current_menu_item.options) + current_menu_item.destroy! + end end redirect_to action: :edit, id: current_page @@ -141,11 +145,11 @@ class WikiMenuItemsController < ApplicationController private def wiki_menu_item_params - @wiki_menu_item_params ||= params.require(:menu_items_wiki_menu_item).permit(:name, :title, :navigatable_id, :parent_id, - :setting, :new_wiki_page, :index_page) + @wiki_menu_item_params ||= params.expect(menu_items_wiki_menu_item: %i[name title navigatable_id parent_id + setting new_wiki_page index_page]) end - def get_data_from_params(params) + def get_data_from_params(params) # rubocop:disable Metrics/AbcSize wiki = @project.wiki @page = wiki.find_page(params[:id]) diff --git a/spec/controllers/wiki_menu_items_controller_spec.rb b/spec/controllers/wiki_menu_items_controller_spec.rb index 3637c3a8a74..4c39761318c 100644 --- a/spec/controllers/wiki_menu_items_controller_spec.rb +++ b/spec/controllers/wiki_menu_items_controller_spec.rb @@ -135,7 +135,7 @@ RSpec.describe WikiMenuItemsController do before do post :replace_main_menu_item, params: { - project_id: project, + project_id: project.id, id: wiki_page.id, wiki_page: { id: selected_page.id } } @@ -161,7 +161,7 @@ RSpec.describe WikiMenuItemsController do before do post :replace_main_menu_item, params: { - project_id: project, + project_id: project.id, id: wiki_page.id, wiki_page: { id: wiki_page.id } } From 8cfa698f041c4dd7f6c9b7c69c297a52943e4901 Mon Sep 17 00:00:00 2001 From: Christophe Bliard Date: Fri, 13 Feb 2026 11:02:47 +0100 Subject: [PATCH 290/293] Notify operations by email when seed-all-locales workflow fails [skip ci] --- .github/workflows/seed-all-locales.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/seed-all-locales.yml b/.github/workflows/seed-all-locales.yml index d2c9d389e90..889c5367bdd 100644 --- a/.github/workflows/seed-all-locales.yml +++ b/.github/workflows/seed-all-locales.yml @@ -110,3 +110,22 @@ jobs: SILENCE_SQL_LOGS: 1 OPENPROJECT_EDITION: ${{ matrix.edition }} run: ruby script/i18n/test_seed_all_locales ${{ matrix.locale }} + + notify: + needs: [prepare, seed] + if: ${{ always() && contains(needs.*.result, 'failure') }} + uses: ./.github/workflows/email-notification.yml + secrets: inherit + with: + to: operations@openproject.com + subject: "Seeding with some locales failed on ${{ needs.prepare.outputs.ref }}" + body: | + The seed-all-locales workflow has failed. + + Branch: ${{ needs.prepare.outputs.ref }} + Editions tested: standard, bim + Trigger: ${{ github.event_name }} + + Some locale/edition combinations failed to seed correctly. + Check the workflow run for details on which locales failed: + ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} From 688626a7d47e6f0f5baac714c6023a3b19e807ff Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Fri, 13 Feb 2026 11:05:51 +0100 Subject: [PATCH 291/293] Fix wiki specs and view --- app/controllers/wiki_menu_items_controller.rb | 2 +- .../wiki_menu_items/select_main_menu_item.html.erb | 3 +-- spec/features/menu_items/wiki_menu_item_spec.rb | 11 ++++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/controllers/wiki_menu_items_controller.rb b/app/controllers/wiki_menu_items_controller.rb index 104f857d0ef..bb7351753e8 100644 --- a/app/controllers/wiki_menu_items_controller.rb +++ b/app/controllers/wiki_menu_items_controller.rb @@ -193,6 +193,6 @@ class WikiMenuItemsController < ApplicationController end menu_item.options = options - menu_item.save + menu_item.save! end end diff --git a/app/views/wiki_menu_items/select_main_menu_item.html.erb b/app/views/wiki_menu_items/select_main_menu_item.html.erb index 50ef64461e8..6a16a9842e5 100644 --- a/app/views/wiki_menu_items/select_main_menu_item.html.erb +++ b/app/views/wiki_menu_items/select_main_menu_item.html.erb @@ -43,8 +43,7 @@ See COPYRIGHT and LICENSE files for more details. <%= f.select :id, wiki_page_options_for_select(@possible_wiki_pages), { label: WikiPage.human_attribute_name(:title) }, - { size: @possible_wiki_pages.size, - id: "main-menu-item-select" } %> + { id: "main-menu-item-select" } %>

    <%= submit_tag t(:button_save), class: "button -primary" %> diff --git a/spec/features/menu_items/wiki_menu_item_spec.rb b/spec/features/menu_items/wiki_menu_item_spec.rb index bd3cfdc7be6..be9d357814a 100644 --- a/spec/features/menu_items/wiki_menu_item_spec.rb +++ b/spec/features/menu_items/wiki_menu_item_spec.rb @@ -57,7 +57,7 @@ RSpec.describe "Wiki menu items", end before do - allow(User).to receive(:current).and_return user + login_as(user) end context "with identical names" do @@ -171,15 +171,16 @@ RSpec.describe "Wiki menu items", click_link_or_button "Save" + wait_for_network_idle + # Because it is the last wiki menu item, the user is prompted to select another menu item select another_wiki_page.title, from: "main-menu-item-select" click_link_or_button "Save" - expect(page) - .to have_no_css(".main-menu--children-menu-header", text: other_wiki_page.title) + wait_for_network_idle - expect(page) - .to have_css(".main-menu--children-menu-header", text: another_wiki_page.title) + expect(page).to have_css(".main-menu--children-menu-header", text: another_wiki_page.title, visible: :all) + expect(page).to have_no_css(".main-menu--children-menu-header", text: other_wiki_page.title, visible: :all) end end From 20f78b1cb0837348587d1cfa6e5b284e387caf01 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Fri, 13 Feb 2026 11:17:27 +0100 Subject: [PATCH 292/293] Allow skipping capybara logs and document test running for feature specs --- .../testing/running-tests-locally/README.md | 27 ++++++++++++------- spec/support/capybara_browser_logs.rb | 1 + 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/docs/development/testing/running-tests-locally/README.md b/docs/development/testing/running-tests-locally/README.md index 4cef77d2196..b06ba40f7d6 100644 --- a/docs/development/testing/running-tests-locally/README.md +++ b/docs/development/testing/running-tests-locally/README.md @@ -73,7 +73,11 @@ RAILS_ENV=test bundle exec rspec spec/models/work_package_spec.rb spec/models/pr ## System tests -System tests are also called *rspec feature specs* and use [Capybara](https://rubydoc.info/github/teamcapybara/capybara/master) and [Selenium](https://www.selenium.dev/documentation/webdriver/) to run. They are automatically executed with an actual browser when `js: true` is set. +System tests are also called _rspec feature specs_ and use [Capybara](https://rubydoc.info/github/teamcapybara/capybara/master) and [Selenium](https://www.selenium.dev/documentation/webdriver/) to run. They are automatically executed with an actual browser when `js: true` is set. + +To run feature specs, it is important that the frontend assets are being served. This is done by running `npm run serve` +in a separate terminal tab. This will start the Angular CLI and serve the frontend assets on `http://localhost:4200`. +Otherwise, the tests will fail because JavaScript and CSS assets are not available. System tests are located in `spec/features`. Use the following command to run individual test: @@ -81,6 +85,10 @@ System tests are located in `spec/features`. Use the following command to run in RAILS_ENV=test bundle exec rspec spec/features/auth/login_spec.rb ``` +When feature specs are run and a failure occurs, the browser logs are printed to the console. This can be helpful for +debugging, but it can also be overwhelming when there are many logs. To disable this behavior, set the environment +variable `SKIP_CAPYBARA_BROWSER_LOGS` to `true` in your `.env` file or export it in your terminal session: + ### Dependencies For the javascript dependent integration tests, you have to install Chrome and Firefox, to run them locally. @@ -91,7 +99,7 @@ Capybara uses Selenium to drive the browser and perform the actions we describe Almost all system tests depend on the browser for testing, you will need to have the Angular CLI running to serve frontend assets. -So with `npm run serve` running and completed in one tab, run the test using `rspec` as for the unit tests: +So with `npm run serve` running and completed in one tab, run the test using `rspec` as for the unit tests: ```shell RAILS_ENV=test bundle exec rspec ./modules/documents/spec/features/attachment_upload_spec.rb[1:1:1:1] @@ -99,7 +107,7 @@ RAILS_ENV=test bundle exec rspec ./modules/documents/spec/features/attachment_up The tests will generally run a lot slower due to the whole application being run end-to-end, but these system tests will provide the most elaborate tests possible. -You can also run *all* feature specs locally with this command. This is not recommended due to the required execution time. Instead, prefer to select individual tests that you would like to test and let GitHub Actions CI test the entire suite. +You can also run _all_ feature specs locally with this command. This is not recommended due to the required execution time. Instead, prefer to select individual tests that you would like to test and let GitHub Actions CI test the entire suite. ```shell RAILS_ENV=test bundle exec rake parallel:features -- --group-number 1 --only-group 1 @@ -119,7 +127,7 @@ Either save the driver under `C:\Windows\system32` to make it available or add i **3) Find out your WSL ethernet adapter IP** -You can do this by opening a powershell and running ```wsl cat /etc/resolv.conf `| grep nameserver `| cut -d ' ' -f 2```. Alternatively looking for the adapter's IP in the output of `ipconfig` works too. +You can do this by opening a powershell and running ``wsl cat /etc/resolv.conf `| grep nameserver `| cut -d ' ' -f 2``. Alternatively looking for the adapter's IP in the output of `ipconfig` works too. It will be called something like "Ethernet adapter vEthernet (WSL)". **4) Download Selenium hub** @@ -185,9 +193,9 @@ You can fix this either by accessing a page locally (if the rails server is runn You can run the specs with the following commands: -* `bundle exec rake spec` Run all core specs and feature tests. Again ensure that the Angular CLI is running for these to work. This will take a long time locally, and it is not recommend to run the entire suite locally. Instead, wait for the test suite run to be performed on GitHub Actions CI as part of your pull request. +- `bundle exec rake spec` Run all core specs and feature tests. Again ensure that the Angular CLI is running for these to work. This will take a long time locally, and it is not recommend to run the entire suite locally. Instead, wait for the test suite run to be performed on GitHub Actions CI as part of your pull request. -* `SPEC_OPTS="--seed 12935" bundle exec rake spec` Run the core specs with the seed 12935. Use this to control in what order the tests are run to identify order-dependent failures. You will find the seed that GitHub Actions CI used in their log output. +- `SPEC_OPTS="--seed 12935" bundle exec rake spec` Run the core specs with the seed 12935. Use this to control in what order the tests are run to identify order-dependent failures. You will find the seed that GitHub Actions CI used in their log output. ## Parallel testing @@ -255,7 +263,7 @@ To easily change the RSpec examples being run without relaunching `watchexec` ev ## Manual acceptance tests -* Sometimes you want to test things manually. Always remember: If you test something more than once, write an automated test for it. +- Sometimes you want to test things manually. Always remember: If you test something more than once, write an automated test for it. ### Accessing a local OpenProject instance from a VM or mobile phone @@ -287,6 +295,7 @@ you can access both from inside a VM with nat/bridged networking as follows: # Start ng serve middleware binding to the interface given by FE_HOST or on localhost if not defined FE_HOST= PROXY_HOSTNAME= npm run serve ``` + On npm run serve, you want to ensure it logs the correct hostname: ```log @@ -312,9 +321,9 @@ OPENPROJECT_CLI_PROXY="http://$LOCAL_IP_ADDR:4200" ### Legacy LDAP tests -OpenProject supports using LDAP for user authentications. To test LDAP +OpenProject supports using LDAP for user authentications. To test LDAP with OpenProject, load the LDAP export from `test/fixtures/ldap/test-ldap.ldif` -into a testing LDAP server. Test that the ldap server can be accessed +into a testing LDAP server. Test that the ldap server can be accessed at 127.0.0.1 on port 389. Setting up the test ldap server is beyond the scope of this documentation. diff --git a/spec/support/capybara_browser_logs.rb b/spec/support/capybara_browser_logs.rb index 2bede2f3843..fa2b7efc3b0 100644 --- a/spec/support/capybara_browser_logs.rb +++ b/spec/support/capybara_browser_logs.rb @@ -9,6 +9,7 @@ module Capybara::BrowserLogs class << self def after_failed_example(example) + return if ENV["SKIP_CAPYBARA_BROWSER_LOGS"] == "true" return unless failed?(example) return unless example.example_group.include?(Capybara::DSL) return if Capybara.page.current_url.blank? From 00132c0da7f1b0aeffa2d5c1d1854625938e552b Mon Sep 17 00:00:00 2001 From: Christophe Bliard Date: Fri, 13 Feb 2026 15:20:22 +0100 Subject: [PATCH 293/293] Use better job names [skip ci] --- .github/workflows/docker.yml | 2 +- .github/workflows/seed-all-locales.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 7c29ea86e23..338b227b03e 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -344,7 +344,7 @@ jobs: - name: Inspect image run: | docker buildx imagetools inspect ${{ needs.setup.outputs.registry_image }}:${{ steps.meta.outputs.version }} - notify: + notify-failure: needs: [setup, build, merge] if: ${{ always() && contains(needs.*.result, 'failure') }} uses: ./.github/workflows/email-notification.yml diff --git a/.github/workflows/seed-all-locales.yml b/.github/workflows/seed-all-locales.yml index 889c5367bdd..0e88f27f4ef 100644 --- a/.github/workflows/seed-all-locales.yml +++ b/.github/workflows/seed-all-locales.yml @@ -111,7 +111,7 @@ jobs: OPENPROJECT_EDITION: ${{ matrix.edition }} run: ruby script/i18n/test_seed_all_locales ${{ matrix.locale }} - notify: + notify-failure: needs: [prepare, seed] if: ${{ always() && contains(needs.*.result, 'failure') }} uses: ./.github/workflows/email-notification.yml