diff --git a/app/components/activities/item_component.html.erb b/app/components/activities/item_component.html.erb
index 00520787386..121c89daf3b 100644
--- a/app/components/activities/item_component.html.erb
+++ b/app/components/activities/item_component.html.erb
@@ -34,7 +34,10 @@ See COPYRIGHT and LICENSE files for more details.
<% else %>
<%= @event.event_title %>
<% end %>
- <%= project_suffix %>
+ <% suffix = project_suffix %>
+ <% if suffix.present? %>
+ (<%= suffix %>)
+ <% end %>
<%=
render(
diff --git a/app/components/activities/item_component.rb b/app/components/activities/item_component.rb
index 803fead8871..923e7415169 100644
--- a/app/components/activities/item_component.rb
+++ b/app/components/activities/item_component.rb
@@ -45,8 +45,7 @@ class Activities::ItemComponent < ViewComponent::Base
return if activity_is_from_current_project?
kind = activity_is_from_subproject? ? "subproject" : "project"
- suffix = I18n.t("events.title.#{kind}", name: link_to(@event.project.name, @event.project))
- "(#{suffix})".html_safe # rubocop:disable Rails/OutputSafety
+ helpers.t("events.title.#{kind}_html", name: link_to(@event.project.name, @event.project))
end
def display_user?
diff --git a/app/components/activities/item_subtitle_component.html.erb b/app/components/activities/item_subtitle_component.html.erb
index 4721b1d0aa2..02b65f3805d 100644
--- a/app/components/activities/item_subtitle_component.html.erb
+++ b/app/components/activities/item_subtitle_component.html.erb
@@ -29,10 +29,11 @@ See COPYRIGHT and LICENSE files for more details.
<%=
+ # OG: html_safe usage has been double-checked and would otherwise require a lot of i18n key change
I18n.t(
i18n_key,
- user: user_html,
- datetime: datetime_html
- ).html_safe # OG: html_safe usage has been double-checked and would otherwise require a lot of i18n key change
+ user: h(user_html),
+ datetime: h(datetime_html)
+ ).html_safe # rubocop:disable Rails/OutputSafety
%>
diff --git a/app/components/enterprise_edition/plan_for_feature.rb b/app/components/enterprise_edition/plan_for_feature.rb
index 64d2bee683f..747708b6953 100644
--- a/app/components/enterprise_edition/plan_for_feature.rb
+++ b/app/components/enterprise_edition/plan_for_feature.rb
@@ -48,7 +48,7 @@ module EnterpriseEdition
def description
@description || begin
if I18n.exists?(:description_html, scope: i18n_scope)
- I18n.t(:description_html, scope: i18n_scope).html_safe
+ helpers.t(:description_html, scope: i18n_scope)
else
I18n.t(:description, scope: i18n_scope)
end
@@ -85,7 +85,7 @@ module EnterpriseEdition
I18n.t("ee.upsell.plan_name", plan: plan.capitalize)
end
- I18n.t("ee.upsell.plan_text_html", plan_name:).html_safe
+ helpers.t("ee.upsell.plan_text_html", plan_name:)
end
end
end
diff --git a/app/components/enterprise_edition/trial_teaser_component.rb b/app/components/enterprise_edition/trial_teaser_component.rb
index 016be7929e1..3af8233fa05 100644
--- a/app/components/enterprise_edition/trial_teaser_component.rb
+++ b/app/components/enterprise_edition/trial_teaser_component.rb
@@ -65,7 +65,7 @@ module EnterpriseEdition
end
def description
- I18n.t("ee.teaser.description", trial_plan: plan_name).html_safe
+ helpers.t("ee.teaser.description_html", trial_plan: plan_name)
end
def plan_name
diff --git a/app/components/placeholder_users/show_page_header_component.html.erb b/app/components/placeholder_users/show_page_header_component.html.erb
index 80afe8f33a9..c04ab856621 100644
--- a/app/components/placeholder_users/show_page_header_component.html.erb
+++ b/app/components/placeholder_users/show_page_header_component.html.erb
@@ -28,7 +28,11 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
<%=
render(Primer::OpenProject::PageHeader.new) do |header|
- header.with_title { "#{avatar @placeholder_user} #{h(@placeholder_user.name)}".html_safe }
+ header.with_title do
+ concat avatar(@placeholder_user)
+ concat @placeholder_user.name
+ end
+
header.with_breadcrumbs(breadcrumb_items)
if @current_user.allowed_globally?(:manage_placeholder_user)
diff --git a/app/components/projects/row_component.html.erb b/app/components/projects/row_component.html.erb
index cff17907e37..d92127cfd5e 100644
--- a/app/components/projects/row_component.html.erb
+++ b/app/components/projects/row_component.html.erb
@@ -27,9 +27,10 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
-
- <%= "class=\"#{row_css_class}\"".html_safe if row_css_class %>>
+<%= content_tag(:tr,
+ id: row_css_id,
+ class: row_css_class
+ ) do %>
<% columns.each do |column| %>
<%= column_value(column) %>
@@ -40,4 +41,4 @@ See COPYRIGHT and LICENSE files for more details.
<%= link %>
<% end %>
-
+<% end %>
diff --git a/app/components/projects/row_component.rb b/app/components/projects/row_component.rb
index 9d81d167966..e09c722ee59 100644
--- a/app/components/projects/row_component.rb
+++ b/app/components/projects/row_component.rb
@@ -211,17 +211,15 @@ module Projects
def project_status
return nil unless user_can_view_project_attributes?
- content = "".html_safe
-
status_code = project.status_code
-
if status_code
classes = helpers.project_status_css_class(status_code)
- content << content_tag(:span, "", class: "project-status--bulb -inline #{classes}")
- content << content_tag(:span, helpers.project_status_name(status_code), class: "project-status--name #{classes}")
- end
- content
+ capture do
+ concat content_tag(:span, "", class: "project-status--bulb -inline #{classes}")
+ concat content_tag(:span, helpers.project_status_name(status_code), class: "project-status--name #{classes}")
+ end
+ end
end
def status_explanation
@@ -250,7 +248,7 @@ module Projects
def row_css_class
classes = %w[basics context-menu--reveal op-project-row-component]
- classes << project_css_classes
+ classes += project_css_classes
classes << row_css_level_classes
classes.join(" ")
@@ -269,13 +267,13 @@ module Projects
end
def project_css_classes
- s = " project ".html_safe
+ output = ["project"]
- s << " root" if project.root?
- s << " child" if project.child?
- s << (project.leaf? ? " leaf" : " parent")
+ output << "root" if project.root?
+ output << "child" if project.child?
+ output << (project.leaf? ? "leaf" : "parent")
- s
+ output
end
def column_css_class(column)
diff --git a/app/components/projects/table_component.html.erb b/app/components/projects/table_component.html.erb
index ca4087a80e9..0de960bd03b 100644
--- a/app/components/projects/table_component.html.erb
+++ b/app/components/projects/table_component.html.erb
@@ -31,7 +31,10 @@ See COPYRIGHT and LICENSE files for more details.
diff --git a/app/views/admin/info.html.erb b/app/views/admin/info.html.erb
index 21d2e9821d2..e59a882016b 100644
--- a/app/views/admin/info.html.erb
+++ b/app/views/admin/info.html.erb
@@ -58,14 +58,12 @@ See COPYRIGHT and LICENSE files for more details.
scheme: :warning,
icon: :alert
)) do
- <<~STR.html_safe
- Starting with OpenProject 16.0, PostgreSQL 16 is required to use OpenProject.
- Your installation will remain functional with your current database, but anticipate incompatability
- in future releases.
-
- We have prepared #{static_link_to(:postgres_17_upgrade, label: 'upgrade guides for all installation methods')}.
- You can perform the upgrade ahead of the next release at any time by following the guides.
- STR
+ link_translate(
+ :"admin.info.database_deprecation_html",
+ links: {
+ upgrade_guide: %i[postgres_17_upgrade]
+ }
+ )
end
component.with_attribute(key: "Deprecation warning", value:)
end
@@ -103,7 +101,7 @@ end %>
component.with_attribute(
key: t(:label_storage_for),
- value: safe_join(entries[:labels], " ".html_safe)
+ value: safe_join(entries[:labels], tag(:br))
)
component.with_attribute(
diff --git a/app/views/admin/settings/authentication_settings/_passwords.html.erb b/app/views/admin/settings/authentication_settings/_passwords.html.erb
index d577d74850e..d6139ab465c 100644
--- a/app/views/admin/settings/authentication_settings/_passwords.html.erb
+++ b/app/views/admin/settings/authentication_settings/_passwords.html.erb
@@ -29,9 +29,9 @@ See COPYRIGHT and LICENSE files for more details.
<%=
settings_primer_form_with(scope: :settings,
- url: admin_settings_authentication_path(tab: params[:tab]),
- data: { turbo_method: :patch },
- method: :patch) do |f|
+ url: admin_settings_authentication_path(tab: params[:tab]),
+ data: { turbo_method: :patch },
+ method: :patch) do |f|
render_inline_settings_form(f) do |form|
disabled = OpenProject::Configuration.disable_password_login?
@@ -42,10 +42,8 @@ See COPYRIGHT and LICENSE files for more details.
icon: :info,
my: 2
) do
- I18n.t(
- :note_password_login_disabled,
- configuration: static_link_to(:disable_password_login, label: I18n.t('label_configuration'))
- ).html_safe
+ link_translate(:note_password_login_disabled,
+ links: { configuration_url: %i[disable_password_login] })
end
end
end
@@ -64,7 +62,7 @@ See COPYRIGHT and LICENSE files for more details.
group.check_box(value:,
label: I18n.t("label_password_rule_#{value}"),
checked: OpenProject::Passwords::Evaluator.active_rule?(value),
- )
+ )
end
end
diff --git a/app/views/admin/settings/authentication_settings/_sso.html.erb b/app/views/admin/settings/authentication_settings/_sso.html.erb
index 8bec979a71b..00010a692ff 100644
--- a/app/views/admin/settings/authentication_settings/_sso.html.erb
+++ b/app/views/admin/settings/authentication_settings/_sso.html.erb
@@ -37,10 +37,10 @@ See COPYRIGHT and LICENSE files for more details.
name: :omniauth_direct_login_provider,
input_width: :medium,
label: I18n.t(:setting_omniauth_direct_login_provider),
- caption: I18n.t(
+ caption: t(
"settings.authentication.omniauth_direct_login_hint_html",
internal_path: internal_signin_url
- ).html_safe,
+ ),
include_blank: I18n.t(:label_none_parentheses)
) do |select|
AuthProvider
diff --git a/app/views/members/_member_form.html.erb b/app/views/members/_member_form.html.erb
index 0d9a4b267c8..5db509c6a23 100644
--- a/app/views/members/_member_form.html.erb
+++ b/app/views/members/_member_form.html.erb
@@ -102,7 +102,11 @@ See COPYRIGHT and LICENSE files for more details.
-
<%= I18n.t("warning_user_limit_reached#{'_admin' if current_user.admin?}", upgrade_url: OpenProject::Enterprise.upgrade_url).html_safe %>
diff --git a/app/views/project_mailer/copy_project_failed.html.erb b/app/views/project_mailer/copy_project_failed.html.erb
index a47494ba1fe..ad978bfa6c7 100644
--- a/app/views/project_mailer/copy_project_failed.html.erb
+++ b/app/views/project_mailer/copy_project_failed.html.erb
@@ -31,4 +31,4 @@ See COPYRIGHT and LICENSE files for more details.
-<%= @errors.join(" ".html_safe) %>
+<%= safe_join @errors, tag(:br) %>
diff --git a/app/views/repositories/_properties.html.erb b/app/views/repositories/_properties.html.erb
new file mode 100644
index 00000000000..d7e3afb59bd
--- /dev/null
+++ b/app/views/repositories/_properties.html.erb
@@ -0,0 +1,39 @@
+<%#-- copyright
+OpenProject is an open source project management software.
+Copyright (C) the OpenProject GmbH
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 3.
+
+OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
+Copyright (C) 2006-2013 Jean-Philippe Lang
+Copyright (C) 2010-2013 the ChiliProject Team
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+See COPYRIGHT and LICENSE files for more details.
+
+++#%>
+
+<% if @properties.present? %>
+
+ <% @properties.keys.sort.each do |property| %>
+
+<% end %>
diff --git a/app/views/repositories/changes.html.erb b/app/views/repositories/changes.html.erb
index 79c5ac7f0db..255628fcbfb 100644
--- a/app/views/repositories/changes.html.erb
+++ b/app/views/repositories/changes.html.erb
@@ -32,7 +32,7 @@ See COPYRIGHT and LICENSE files for more details.
<%= render partial: "link_to_functions" %>
-<%= render_properties(@properties) %>
+<%= render partial: "properties" %>
<%= unless @changesets.empty?
render(
diff --git a/app/views/repositories/destroy_info.html.erb b/app/views/repositories/destroy_info.html.erb
index 7695ef97592..ea64225adb2 100644
--- a/app/views/repositories/destroy_info.html.erb
+++ b/app/views/repositories/destroy_info.html.erb
@@ -42,12 +42,15 @@ See COPYRIGHT and LICENSE files for more details.
<%= styled_form_tag(project_repository_path(@project), method: :delete, class: "danger-zone") do %>
<%= t("repositories.destroy.info_not_managed") %>
@@ -106,7 +116,7 @@ See COPYRIGHT and LICENSE files for more details.
<%= link_to project_settings_repository_path(@project),
title: t(:button_cancel),
class: "button -with-icon icon-cancel" do %>
- <%= t(:button_cancel) %>
+ <%= t(:button_cancel) %>
<% end %>
diff --git a/app/views/repositories/diff.html.erb b/app/views/repositories/diff.html.erb
index e598558ae2a..587cc82e30b 100644
--- a/app/views/repositories/diff.html.erb
+++ b/app/views/repositories/diff.html.erb
@@ -65,14 +65,11 @@ See COPYRIGHT and LICENSE files for more details.
<%= render partial: 'common/diff', locals: { diff: @diff, diff_type: @diff_type } %>
<% end -%>
<%= other_formats_links do |f| %>
- <% unidiff_link = f.link_to 'Diff', url: permitted_params.repository_diff.to_h, caption: 'Unified diff' %>
- <% if !@path.blank? %>
- <% unidiff_link.gsub!('?', '&') %>
- <% end %>
- <% wrong_url = CGI::escapeHTML(CGI.escape(with_leading_slash(@path))).concat('.diff') %>
- <% good_url = '.diff'.concat('?repo_path=', CGI.escape(without_leading_slash(@path)).gsub('&', '%26')) %>
- <% unidiff_link.gsub!(wrong_url, good_url) %>
- <%= unidiff_link.html_safe %>
+ <%
+ unidiff_params = permitted_params.repository_diff.to_h.merge(format: "diff")
+ unidiff_params[:repo_path] = without_leading_slash(@path) if @path.present?
+ %>
+ <%= f.link_to "Diff", url: unidiff_params, caption: "Unified diff" %>
<% end %>
<% html_title(h(with_leading_slash(@path)), 'Diff') -%>
diff --git a/app/views/repositories/show.html.erb b/app/views/repositories/show.html.erb
index 13b4865fa08..2a2a433311b 100644
--- a/app/views/repositories/show.html.erb
+++ b/app/views/repositories/show.html.erb
@@ -34,7 +34,7 @@ See COPYRIGHT and LICENSE files for more details.
<%= render partial: "dir_list" %>
<% end %>
-<%= render_properties(@properties) %>
+<%= render partial: "properties" %>
<%# rev => nil prevents overwriting the rev parameter queried for in the form with the parameter from the request %>
diff --git a/app/views/sharing_mailer/shared_work_package.html.erb b/app/views/sharing_mailer/shared_work_package.html.erb
index f1a3da18d3a..2f15ffa749e 100644
--- a/app/views/sharing_mailer/shared_work_package.html.erb
+++ b/app/views/sharing_mailer/shared_work_package.html.erb
@@ -36,12 +36,12 @@
<%= select_tag "target_role_ids",
- "".html_safe +
- options_from_collection_for_select(@roles, "id", "name", @target_roles && @target_roles.map(&:id)),
+ blank_select_option + options_from_collection_for_select(@roles, "id", "name", @target_roles && @target_roles.map(&:id)),
multiple: true,
size: 20,
class: "form--select" %>
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 53810ac3649..b0a4b9d921e 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -436,6 +436,15 @@ en:
default_transitions: "Default transitions"
user_author: "User is author"
user_assignee: "User is assignee"
+ info:
+ database_deprecation_html: >
+ Starting with OpenProject 16.0, PostgreSQL 16 is required to use OpenProject.
+ Your installation will remain functional with your current database, but anticipate incompatability
+ in future releases.
+
+ We have prepared [upgrade guides for all installation methods](upgrade_guide).
+ You can perform the upgrade ahead of the next release at any time by following the guides.
+
authentication:
login_and_registration: "Login and registration"
@@ -1101,9 +1110,9 @@ en:
groups:
member_in_these_groups: "This user is currently a member of the following groups:"
no_results_title_text: This user is currently not a member in any group.
- summary_with_more: Member of %{names} and %{count_link}.
+ summary_with_more_html: Member of %{names} and %{count_link}.
more: "%{count} more"
- summary: Member of %{names}.
+ summary_html: Member of %{names}.
memberships:
no_results_title_text: This user is currently not a member of a project.
open_profile: "Open profile"
@@ -1219,6 +1228,9 @@ en:
work_days_count:
one: "1 working day"
other: "%{count} working days"
+ user_preferences:
+ disable_keyboard_shortcuts_caption: >
+ You can choose to disable default [keyboard shortcuts](docs_url) if you use a screen reader or want to avoid accidentally triggering an action with a shortcut.
page:
text: "Text"
placeholder_users:
@@ -1227,7 +1239,7 @@ en:
You do not have the right to manage members for all projects that the placeholder user is a member of.
delete_tooltip: "Delete placeholder user"
deletion_info:
- heading: "Delete placeholder user %{name}"
+ heading_html: "Delete placeholder user %{name}"
data_consequences: >
All occurrences of the placeholder user (e.g., as assignee, responsible or other user values)
will be reassigned to an account called "Deleted user".
@@ -1236,7 +1248,7 @@ en:
it will not be possible to distinguish the data the user created from
the data of another deleted account.
irreversible: "This action is irreversible"
- confirmation: "Enter the placeholder user name %{name} to confirm the deletion."
+ confirmation_html: "Enter the placeholder user name %{name} to confirm the deletion."
priorities:
edit:
priority_color_text: |
@@ -1272,7 +1284,7 @@ en:
Check this option to exclude work packages with this status from totals of Work,
Remaining work, and % Complete in a hierarchy.
status_percent_complete_text: |-
- In status-based progress calculation mode, the % Complete of a work
+ In [status-based progress calculation mode](setting_url), the % Complete of a work
package is automatically set to this value when this status is selected.
Ignored in work-based mode.
status_readonly_html: |
@@ -1560,7 +1572,7 @@ en:
account for you or change the self registration limit for this provider.
login_with_auth_provider: "or sign in with your existing account"
signup_with_auth_provider: "or sign up using"
- auth_source_login: Please login as %{login} to activate your account.
+ auth_source_login_html: Please login as %{login} to activate your account.
omniauth_login: Please login to activate your account.
actionview_instancetag_blank_option: "Please select"
@@ -1853,8 +1865,6 @@ en:
button_update_user_information: "Update profile"
comments_sorting: "Display work package activity sorted by"
disable_keyboard_shortcuts: "Disable keyboard shortcuts"
- 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"
impaired: "Accessibility mode"
auto_hide_popups: "Automatically hide success banners"
@@ -2618,7 +2628,7 @@ en:
You will need to generate a backup token to be able to create a backup.
Each time you want to request a backup you will have to provide this token.
You can delete the backup token to disable backups for this user.
- verification: >
+ verification_html: >
Enter %{word} to confirm you want to %{action} the backup token.
verification_word_reset: reset
verification_word_create: create
@@ -3077,7 +3087,7 @@ en:
title:
one: "One day left of %{trial_plan} trial token"
other: "%{count} days left of %{trial_plan} trial token"
- description: "You have access to all %{trial_plan} features."
+ description_html: "You have access to all %{trial_plan} features."
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."
@@ -3096,11 +3106,8 @@ en:
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.
- receive_newsletter_html: >
- I want to receive the OpenProject newsletter.
- consent_html: >
- I agree with the terms of service
- and the privacy policy.
+ receive_newsletter: "I want to receive the OpenProject [newsletter](newsletter_url)."
+ consent: "I agree with the [terms of service](tos_url) and the [privacy policy](privacy_url)."
email_calendar_updates:
state:
@@ -3195,8 +3202,8 @@ en:
work_package_edit: "Work Package edited"
work_package_note: "Work Package note added"
title:
- project: "Project: %{name}"
- subproject: "Subproject: %{name}"
+ project_html: "Project: %{name}"
+ subproject_html: "Subproject: %{name}"
export:
dialog:
@@ -3466,9 +3473,9 @@ en:
configuration_guide: "Configuration guide"
get_in_touch: "You have questions? Get in touch with us."
- instructions_after_registration: "You can sign in as soon as your account has been activated by clicking %{signin}."
- instructions_after_logout: "You can sign in again by clicking %{signin}."
- instructions_after_error: "You can try to sign in again by clicking %{signin}. If the error persists, ask your admin for help."
+ instructions_after_registration: "You can sign in as soon as your account has been activated by clicking [here](signin_url)."
+ instructions_after_logout: "You can sign in again by clicking [here](signin_url)."
+ instructions_after_error: "You can try to sign in again by clicking [here](signin_url). If the error persists, ask your admin for help."
menus:
admin:
@@ -3664,6 +3671,8 @@ en:
label_calendar_show: "Show Calendar"
label_category: "Category"
label_completed: Completed
+ label_committed_at_html: "%{committed_revision_link} at %{date}"
+ label_committed_link: "committed revision %{revision_identifier}"
label_consent_settings: "User Consent"
label_wiki_menu_item: Wiki menu item
label_select_main_menu_item: Select new main menu item
@@ -4189,7 +4198,7 @@ en:
label_user: "User"
label_user_and_permission: "Users and permissions"
label_user_named: "User %{name}"
- label_user_activity: "%{value}'s activity"
+ label_user_activity_html: "%{value}'s activity"
label_user_anonymous: "Anonymous"
label_user_menu: "User menu"
label_user_new: "New user"
@@ -4373,7 +4382,7 @@ en:
note: "Note: “%{note}”"
sharing:
work_packages:
- allowed_actions: "You may %{allowed_actions} this work package. This can change depending on your project role and permissions."
+ allowed_actions_html: "You may %{allowed_actions} this work package. This can change depending on your project role and permissions."
create_account: "To access this work package, you will need to create and activate an account on %{instance}."
open_work_package: "Open work package"
subject: "Work package #%{id} was shared with you"
@@ -4476,7 +4485,7 @@ en:
mail_user_activation_limit_reached:
subject: User activation limit reached
- message: |
+ message_html: |
A new user (%{email}) tried to create an account on an OpenProject environment that you manage (%{host}).
The user cannot activate their account since the user limit has been reached.
steps:
@@ -4832,10 +4841,10 @@ en:
info: "Deleting the repository is an irreversible action."
info_not_managed: "Note: This will NOT delete the contents of this repository, as it is not managed by OpenProject."
managed_path_note: "The following directory will be erased: %{path}"
- repository_verification: "Enter the project's identifier %{identifier} to verify the deletion of its repository."
+ repository_verification_html: "Enter the project's identifier %{identifier} to verify the deletion of its repository."
subtitle: "Do you really want to delete the %{repository_type} of the project %{project_name}?"
- subtitle_not_managed: "Do you really want to remove the linked %{repository_type} %{url} from the project %{project_name}?"
- title: "Delete the %{repository_type}"
+ subtitle_not_managed_html: "Do you really want to remove the linked %{repository_type} %{url} from the project %{project_name}?"
+ title_html: "Delete the %{repository_type}"
title_not_managed: "Remove the linked %{repository_type}?"
errors:
build_failed: "Unable to create the repository with the selected configuration. %{reason}"
@@ -4946,7 +4955,7 @@ en:
setting_apiv3_cors_origins_text_html: >
If CORS is enabled, these are the origins that are allowed to access OpenProject API.
- Please check the Documentation on the Origin header on how to specify the expected values.
+ Please check the [Documentation on the Origin header](docs_url) on how to specify the expected values.
setting_apiv3_write_readonly_attributes: "Write access to read-only attributes"
setting_apiv3_write_readonly_attributes_instructions: >
If enabled, the API will allow administrators to write static read-only attributes during creation,
@@ -4956,7 +4965,7 @@ en:
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}.
+ For more information on attributes and supported resources, please see the [API documentation](api_documentation_link).
setting_apiv3_max_page_size: "Maximum API page size"
setting_apiv3_max_page_size_instructions: >
Set the maximum page size the API will respond with.
@@ -5366,7 +5375,10 @@ en:
not_allowed_text: "You do not have the necessary permissions to view this page."
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}"
+ 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.
+ [Click here to learn more](docs_url)
text_formatting:
markdown: "Markdown"
@@ -5647,21 +5659,23 @@ en:
version_status_open: "open"
note: Note
- note_password_login_disabled: "Password login has been disabled by %{configuration}."
+ note_password_login_disabled: "Password login has been disabled through a [configuration setting](configuration_url)."
warning: Warning
warning_attachments_not_saved: "%{count} file(s) could not be saved."
warning_imminent_user_limit: >
You invited more users than are supported by your current plan.
Invited users may not be able to join your OpenProject environment.
- Please upgrade your plan or block existing
+ Please [upgrade your plan](upgrade_url) or block existing
users in order to allow invited and registered users to join.
warning_registration_token_expired: |
The activation email has expired. We sent you a new one to %{email}.
Please click the link inside of it to activate your account.
warning_user_limit_reached: >
- Adding additional users will exceed the current limit. Please contact an administrator to increase the user limit to ensure external users are able to access this instance.
+ Adding additional users will exceed the current limit.
+ Please contact an administrator to increase the user limit to ensure external users are able to access this instance.
warning_user_limit_reached_admin: >
- Adding additional users will exceed the current limit. Please upgrade your plan to be able to ensure external users are able to access this instance.
+ Adding additional users will exceed the current limit.
+ Please [upgrade your plan](upgrade_url) to be able to ensure external users are able to access this instance.
warning_user_limit_reached_instructions: >
You reached your user limit (%{current}/%{max} active users).
Please contact sales@openproject.com to upgrade your Enterprise edition plan and add additional users.
@@ -5730,7 +5744,7 @@ en:
reminders:
label_remind_at: "Date"
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}."
+ create_success_message_html: "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."
sharing:
@@ -5760,9 +5774,11 @@ en:
text_user_limit_reached: "Adding additional users will exceed the current limit. Please contact an administrator to increase the user limit to ensure external users are able to access this %{entity}."
text_user_limit_reached_admins: 'Adding additional users will exceed the current limit. Please upgrade your plan to be able to add more users.'
warning_user_limit_reached: >
- Adding additional users will exceed the current limit. Please contact an administrator to increase the user limit to ensure external users are able to access this %{entity}.
+ Adding additional users will exceed the current limit.
+ Please contact an administrator to increase the user limit to ensure external users are able to access this %{entity}.
warning_user_limit_reached_admin: >
- Adding additional users will exceed the current limit. Please upgrade your plan to be able to ensure external users are able to access this %{entity}.
+ Adding additional users will exceed the current limit.
+ Please [upgrade your plan](upgrade_url) to be able to ensure external users are able to access this %{entity}.
warning_no_selected_user: "Please select users to share this %{entity} with"
warning_locked_user: "The user %{user} is locked and cannot be shared with"
user_details:
diff --git a/config/locales/js-en.yml b/config/locales/js-en.yml
index 084a63c3edd..6e8a9765fc0 100644
--- a/config/locales/js-en.yml
+++ b/config/locales/js-en.yml
@@ -389,8 +389,6 @@ en:
label_collapse_all: "Collapse all"
label_collapse_text: "Collapse text"
label_comment: "Comment"
- label_committed_at: "%{committed_revision_link} at %{date}"
- label_committed_link: "committed revision %{revision_identifier}"
label_contains: "contains"
label_created_on: "created on"
label_edit_comment: "Edit this comment"
diff --git a/config/static_links.yml b/config/static_links.yml
index 7708b89c816..03c503d8288 100644
--- a/config/static_links.yml
+++ b/config/static_links.yml
@@ -201,6 +201,8 @@ sysadmin_docs:
href: https://www.openproject.org/docs/system-admin-guide/authentication/scim/#step-3-choose-an-authentication-method
scim_jwt_authetication_method:
href: https://www.openproject.org/docs/system-admin-guide/authentication/scim/#c-jwt-from-identity-provider
+terms_of_service:
+ href: https://www.openproject.org/terms-of-service/
text_formatting:
href: https://www.openproject.org/docs/user-guide/wysiwyg/
label: :setting_text_formatting
diff --git a/lib/custom_field_form_builder.rb b/lib/custom_field_form_builder.rb
index 836361c992d..74b455567a1 100644
--- a/lib/custom_field_form_builder.rb
+++ b/lib/custom_field_form_builder.rb
@@ -142,15 +142,14 @@ class CustomFieldFormBuilder < TabularFormBuilder
for: custom_field_field_id,
class: classes,
title: custom_field.name do
- output = "".html_safe
- output += custom_field.name
+ capture do
+ concat custom_field.name
- # Render a help text icon
- if options[:help_text]
- output += content_tag("attribute-help-text", "", data: options[:help_text])
+ # Render a help text icon
+ if options[:help_text]
+ concat content_tag("attribute-help-text", "", data: options[:help_text])
+ end
end
-
- output
end
end
end
diff --git a/lib/open_project/object_linking.rb b/lib/open_project/object_linking.rb
index 584285675e6..58b4f628451 100644
--- a/lib/open_project/object_linking.rb
+++ b/lib/open_project/object_linking.rb
@@ -167,7 +167,11 @@ module OpenProject
def project_link_name(project, show_icon)
if show_icon && User.current.member_of?(project)
- icon_wrapper("icon-context icon-star", I18n.t(:description_my_project).html_safe + " ".html_safe) + project.name
+ label = ActiveSupport::SafeBuffer.new
+ label << I18n.t(:description_my_project)
+ label << " ".html_safe
+
+ icon_wrapper("icon-context icon-star", label) + project.name
else
project.name
end
diff --git a/lib/open_project/page_hierarchy_helper.rb b/lib/open_project/page_hierarchy_helper.rb
index b102cadb240..0f1d3efa46c 100644
--- a/lib/open_project/page_hierarchy_helper.rb
+++ b/lib/open_project/page_hierarchy_helper.rb
@@ -39,7 +39,7 @@ module OpenProject
concat render_page_hierarchy(pages, page.id, options) if is_parent
end
end
- chunks.join.html_safe
+ safe_join(chunks)
end
end
diff --git a/lib/open_project/patches/action_view_accessible_errors.rb b/lib/open_project/patches/action_view_accessible_errors.rb
index cc9aa38cd11..8eef9d9f0be 100644
--- a/lib/open_project/patches/action_view_accessible_errors.rb
+++ b/lib/open_project/patches/action_view_accessible_errors.rb
@@ -44,7 +44,11 @@ module ActionView
def wrap_with_error_span(html_tag, object, method)
object_identifier = erroneous_object_identifier(object.object_id.to_s, method)
- "#{html_tag}".html_safe
+ helpers = ActionController::Base.helpers
+
+ helpers.content_tag(:span, id: object_identifier, class: "errorSpan") do
+ helpers.content_tag(:a, "", name: object_identifier) + html_tag
+ end
end
def erroneous_object_identifier(id, method)
diff --git a/lib/redmine/menu_manager/menu_helper.rb b/lib/redmine/menu_manager/menu_helper.rb
index 9b8384173ba..b369d892415 100644
--- a/lib/redmine/menu_manager/menu_helper.rb
+++ b/lib/redmine/menu_manager/menu_helper.rb
@@ -180,10 +180,10 @@ module Redmine::MenuManager::MenuHelper
caption, url, selected = extract_node_details(item, project)
shown_in_main_menu = menu_class == "op-menu"
- link_text = "".html_safe
+ link_text = ActiveSupport::SafeBuffer.new
if item.icon(project).present?
- link_text += render(Primer::Beta::Octicon.new(
+ link_text << render(Primer::Beta::Octicon.new(
icon: item.icon,
mr: shown_in_main_menu ? 3 : 0,
size: shown_in_main_menu ? :small : :medium
@@ -192,7 +192,7 @@ module Redmine::MenuManager::MenuHelper
badge_class = item.badge(project:).present? ? " #{menu_class}--item-title_has-badge" : ""
- link_text += content_tag(:span,
+ link_text << content_tag(:span,
class: "#{menu_class}--item-title#{badge_class}",
lang: menu_item_locale(item)) do
title_text = content_tag(:span, caption, class: "ellipsis") + badge_for(item)
@@ -205,7 +205,7 @@ module Redmine::MenuManager::MenuHelper
end
if item.icon_after.present?
- link_text += render(Primer::Beta::Octicon.new(icon: item.icon_after, classes: "trailing-icon"))
+ link_text << render(Primer::Beta::Octicon.new(icon: item.icon_after, classes: "trailing-icon"))
end
html_options = item.html_options(selected:)
@@ -424,8 +424,6 @@ module Redmine::MenuManager::MenuHelper
key = item.badge(project: @project)
if key.present?
content_tag("span", I18n.t(key), class: "main-item--badge")
- else
- "".html_safe
end
end
diff --git a/lib/tabular_form_builder.rb b/lib/tabular_form_builder.rb
index 4c55d02171c..9147756edd6 100644
--- a/lib/tabular_form_builder.rb
+++ b/lib/tabular_form_builder.rb
@@ -207,7 +207,7 @@ class TabularFormBuilder < ActionView::Helpers::FormBuilder
if prefix
ret.prepend content_tag(:span,
- prefix.html_safe,
+ prefix,
class: "form--field-affix",
id: options[:prefix_id],
"aria-hidden": true)
@@ -215,7 +215,7 @@ class TabularFormBuilder < ActionView::Helpers::FormBuilder
if suffix
ret.concat content_tag(:span,
- suffix.html_safe,
+ suffix,
class: "form--field-affix",
id: options[:suffix_id],
"aria-hidden": true)
diff --git a/lib_static/redmine/i18n.rb b/lib_static/redmine/i18n.rb
index e6b739df5a0..597a25b87ed 100644
--- a/lib_static/redmine/i18n.rb
+++ b/lib_static/redmine/i18n.rb
@@ -290,7 +290,7 @@ module Redmine
href:,
target:,
underline:,
- data: { allow_external_link: true },
+ data: { allow_external_link: true }
)
component.with_trailing_visual_icon(icon: :"link-external") if external
component.with_content(text)
diff --git a/lookbook/docs/patterns/02-forms.md.erb b/lookbook/docs/patterns/02-forms.md.erb
index 47f84f5bb0e..63c317a9891 100644
--- a/lookbook/docs/patterns/02-forms.md.erb
+++ b/lookbook/docs/patterns/02-forms.md.erb
@@ -439,8 +439,7 @@ class Admin::Settings::GeneralSettingsForm < ApplicationForm
name: :per_page_options,
label: I18n.t("setting_per_page_options"),
value: Setting[:per_page_options],
- caption: "#{I18n.t(:text_comma_separated)} " \
- "#{I18n.t(:text_notice_too_many_values_are_inperformant)}".html_safe)
+ caption: safe_join [I18n.t(:text_comma_separated), helpers.tag(:br), I18n.t(:text_notice_too_many_values_are_inperformant)],
disabled: !Setting.per_page_options_writable?
)
general_form.text_field(
@@ -492,8 +491,9 @@ class Admin::Settings::GeneralSettingsForm < ApplicationForm
settings_form do |general_form|
general_form.text_field(name: :app_title)
general_form.text_field(name: :per_page_options,
- caption: "#{I18n.t(:text_comma_separated)} " \
- "#{I18n.t(:text_notice_too_many_values_are_inperformant)}".html_safe)
+ caption: safe_join [I18n.t(:text_comma_separated),
+ helpers.tag(:br),
+ I18n.t(:text_notice_too_many_values_are_inperformant)])
general_form.text_field(name: :activity_days_default,
type: :number)
general_form.text_field(name: :host_name,
diff --git a/modules/auth_saml/app/views/saml/providers/confirm_destroy.html.erb b/modules/auth_saml/app/views/saml/providers/confirm_destroy.html.erb
index a062532c586..57788a906b8 100644
--- a/modules/auth_saml/app/views/saml/providers/confirm_destroy.html.erb
+++ b/modules/auth_saml/app/views/saml/providers/confirm_destroy.html.erb
@@ -36,7 +36,7 @@ See COPYRIGHT and LICENSE files for more details.
<%= t("saml.delete_title") %>
diff --git a/modules/ldap_groups/config/locales/en.yml b/modules/ldap_groups/config/locales/en.yml
index 168e4714cad..fce21491f55 100644
--- a/modules/ldap_groups/config/locales/en.yml
+++ b/modules/ldap_groups/config/locales/en.yml
@@ -48,7 +48,7 @@ en:
title: 'Remove synchronized filter %{name}'
confirmation: "If you continue, the synchronized filter %{name} and all groups %{groups_count} created through it will be removed."
removed_groups: "Warning: This will remove the following groups from OpenProject and remove it from all projects!"
- verification: "Enter the filter name %{name} to verify the deletion."
+ verification_html: "Enter the filter name %{name} to verify the deletion."
form:
group_name_attribute_text: 'Enter the attribute of the LDAP group used for setting the OpenProject group name.'
filter_string_text: 'Enter the RFC4515 LDAP filter that returns groups in your LDAP to synchronize with OpenProject.'
@@ -63,7 +63,7 @@ en:
title: 'Remove synchronized group %{name}'
confirmation: "If you continue, the synchronized group %{name} and all %{users_count} users synchronized through it will be removed."
info: "Note: The OpenProject group itself and members added outside this LDAP synchronization will not be removed."
- verification: "Enter the group's name %{name} to verify the deletion."
+ verification_html: "Enter the group's name %{name} to verify the deletion."
help_text_html: |
This module allows you to set up a synchronization between LDAP and OpenProject groups.
It depends on LDAP groups need to use the groupOfNames / memberOf attribute set to be working with OpenProject.
diff --git a/modules/meeting/app/components/base_errors_component.rb b/modules/meeting/app/components/base_errors_component.rb
index bfa134ec95f..744d95832fe 100644
--- a/modules/meeting/app/components/base_errors_component.rb
+++ b/modules/meeting/app/components/base_errors_component.rb
@@ -52,6 +52,6 @@ class BaseErrorsComponent < ApplicationComponent
def joined_messages
messages = @keys.map { |key| @errors.full_messages_for(key) }.flatten
- helpers.safe_join(messages, " ".html_safe)
+ helpers.safe_join(messages, helpers.tag(:br))
end
end
diff --git a/modules/meeting/app/controllers/meetings_controller.rb b/modules/meeting/app/controllers/meetings_controller.rb
index 3180d9a62f5..182e98c7f40 100644
--- a/modules/meeting/app/controllers/meetings_controller.rb
+++ b/modules/meeting/app/controllers/meetings_controller.rb
@@ -119,13 +119,16 @@ class MeetingsController < ApplicationController
@meeting = call.result
if call.success?
- text = I18n.t(:notice_successful_create)
+ text = ActiveSupport::SafeBuffer.new
+ text << I18n.t(:notice_successful_create)
unless User.current.pref.time_zone?
link = I18n.t(:notice_timezone_missing, zone: formatted_time_zone_offset)
- text += " #{view_context.link_to(link, { controller: '/my', action: :locale, anchor: 'pref_time_zone' },
- class: 'link_to_profile')}"
+ text << " "
+ text << view_context.link_to(link,
+ { controller: "/my", action: :locale, anchor: "pref_time_zone" },
+ class: "link_to_profile")
end
- flash[:notice] = text.html_safe # rubocop:disable Rails/OutputSafety
+ flash[:notice] = text
redirect_to status: :see_other, action: "show", id: @meeting
else
@@ -317,7 +320,7 @@ class MeetingsController < ApplicationController
.call
.on_failure { |call| render_500(message: call.message) }
.on_success do |call|
- send_data call.result, filename: filename_for_content_disposition("#{@meeting.title}.ics")
+ send_data call.result, filename: filename_for_content_disposition("#{@meeting.title}.ics")
end
end
@@ -379,8 +382,8 @@ class MeetingsController < ApplicationController
def exit_draft_mode
call = ::Meetings::UpdateService
- .new(user: current_user, model: @meeting)
- .call({ state: "open", notify: meeting_params[:notify] == "1" })
+ .new(user: current_user, model: @meeting)
+ .call({ state: "open", notify: meeting_params[:notify] == "1" })
if call.success?
deliver_invitation_mails
@@ -419,11 +422,11 @@ class MeetingsController < ApplicationController
.participants
.invited
.find_each do |participant|
- MeetingMailer.invited(
- @meeting,
- participant.user,
- User.current
- ).deliver_later
+ MeetingMailer.invited(
+ @meeting,
+ participant.user,
+ User.current
+ ).deliver_later
end
end
@@ -646,12 +649,13 @@ class MeetingsController < ApplicationController
service = MeetingNotificationService.new(@meeting)
result = service.call(:invited)
- message = if result.success?
- I18n.t(:notice_successful_notification)
- else
- I18n.t(:error_notification_with_errors,
- recipients: result.errors.map(&:name).join("; "))
- end
+ message =
+ if result.success?
+ I18n.t(:notice_successful_notification)
+ else
+ I18n.t(:error_notification_with_errors,
+ recipients: result.errors.map(&:name).join("; "))
+ end
if type == :notify
flash[result.success? ? :notice : :error] = message
@@ -669,11 +673,11 @@ class MeetingsController < ApplicationController
.participants
.invited
.find_each do |participant|
- MeetingSeriesMailer.invited(
- recurring_meeting,
- participant.user,
- User.current
- ).deliver_later
+ MeetingSeriesMailer.invited(
+ recurring_meeting,
+ participant.user,
+ User.current
+ ).deliver_later
end
render_success_flash_message_via_turbo_stream(message: I18n.t(:notice_successful_notification))
diff --git a/modules/meeting/app/controllers/recurring_meetings_controller.rb b/modules/meeting/app/controllers/recurring_meetings_controller.rb
index 751ae75c715..ba41ab814df 100644
--- a/modules/meeting/app/controllers/recurring_meetings_controller.rb
+++ b/modules/meeting/app/controllers/recurring_meetings_controller.rb
@@ -86,7 +86,7 @@ class RecurringMeetingsController < ApplicationController
@recurring_meeting = call.result
if call.success?
- flash[:notice] = I18n.t(:notice_successful_create).html_safe
+ flash[:notice] = I18n.t(:notice_successful_create)
redirect_to project_meeting_path(@recurring_meeting.project, @recurring_meeting.template),
status: :see_other
else
diff --git a/modules/openid_connect/app/views/openid_connect/providers/confirm_destroy.html.erb b/modules/openid_connect/app/views/openid_connect/providers/confirm_destroy.html.erb
index 6dc53156dae..a5457d66dba 100644
--- a/modules/openid_connect/app/views/openid_connect/providers/confirm_destroy.html.erb
+++ b/modules/openid_connect/app/views/openid_connect/providers/confirm_destroy.html.erb
@@ -36,7 +36,8 @@ See COPYRIGHT and LICENSE files for more details.
<%= t("openid_connect.delete_title") %>
<%= text_field_tag :delete_confirmation %>
diff --git a/modules/openid_connect/config/locales/en.yml b/modules/openid_connect/config/locales/en.yml
index c870826f8d9..e8b0d23c425 100644
--- a/modules/openid_connect/config/locales/en.yml
+++ b/modules/openid_connect/config/locales/en.yml
@@ -51,9 +51,9 @@ en:
provider:
delete_warning:
- input_delete_confirmation: Enter the provider name %{name} to confirm deletion.
+ input_delete_confirmation_html: 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:'
+ provider_html: '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.
delete_result_user_count:
zero: No users are currently using this provider. No further action is required.
diff --git a/modules/recaptcha/app/views/recaptcha/admin/show.html.erb b/modules/recaptcha/app/views/recaptcha/admin/show.html.erb
index 6b4bae905f8..565d4d2ec80 100644
--- a/modules/recaptcha/app/views/recaptcha/admin/show.html.erb
+++ b/modules/recaptcha/app/views/recaptcha/admin/show.html.erb
@@ -27,12 +27,12 @@
container_class: "-middle" %>
diff --git a/modules/storages/app/components/storages/admin/oauth_application_info_copy_component.html.erb b/modules/storages/app/components/storages/admin/oauth_application_info_copy_component.html.erb
index dd4f22a0162..2f4efc22692 100644
--- a/modules/storages/app/components/storages/admin/oauth_application_info_copy_component.html.erb
+++ b/modules/storages/app/components/storages/admin/oauth_application_info_copy_component.html.erb
@@ -4,7 +4,11 @@
flex_layout(flex_items: :center) do |credentials_row|
credentials_row.with_row(mb: 3, test_selector: "storage-openproject_oauth_application_warning") do
render(Primer::Alpha::Banner.new(icon: :alert, scheme: :warning)) do
- I18n.t("storages.instructions.oauth_application_details", oauth_application_details_link: oauth_application_details_link).html_safe
+ helpers.link_translate(
+ "storages.instructions.oauth_application_details",
+ links: { oauth_application_details_link: ::Storages::UrlBuilder.url(storage.uri, "settings/admin/openproject") },
+ external: true
+ )
end
end
diff --git a/modules/storages/app/components/storages/admin/oauth_application_info_copy_component.rb b/modules/storages/app/components/storages/admin/oauth_application_info_copy_component.rb
index cb5979463d5..67510d85b20 100644
--- a/modules/storages/app/components/storages/admin/oauth_application_info_copy_component.rb
+++ b/modules/storages/app/components/storages/admin/oauth_application_info_copy_component.rb
@@ -40,16 +40,6 @@ module Storages::Admin
def self.wrapper_key = :storage_openproject_oauth_section
- def oauth_application_details_link
- render(
- Primer::Beta::Link.new(
- href: ::Storages::UrlBuilder.url(storage.uri, "settings/admin/openproject"),
- data: { allow_external_link: true },
- target: "_blank"
- )
- ) { I18n.t("storages.instructions.oauth_application_details_link_text") }
- end
-
def submit_button_options
{
scheme: :primary,
diff --git a/modules/storages/app/components/storages/admin/storages/destroy_confirmation_dialog_component.html.erb b/modules/storages/app/components/storages/admin/storages/destroy_confirmation_dialog_component.html.erb
index 26e88291817..931912bb0b1 100644
--- a/modules/storages/app/components/storages/admin/storages/destroy_confirmation_dialog_component.html.erb
+++ b/modules/storages/app/components/storages/admin/storages/destroy_confirmation_dialog_component.html.erb
@@ -43,7 +43,8 @@ See COPYRIGHT and LICENSE files for more details.
<% dialog.with_additional_details do %>
<% render(Primer::OpenProject::FlexLayout.new) do |flex| %>
<% flex.with_row do %>
- <%= t("storages.delete_warning.storage", file_storage: "#{h(@storage.name)}").html_safe %>
+ <%= t("storages.delete_warning.storage_html",
+ file_storage: content_tag(:strong, @storage.name)) %>
<% end %>
<% flex.with_row do %>
diff --git a/modules/storages/app/components/storages/project_storages/destroy_confirmation_dialog_component.html.erb b/modules/storages/app/components/storages/project_storages/destroy_confirmation_dialog_component.html.erb
index 932ac083f03..dd2c020e3de 100644
--- a/modules/storages/app/components/storages/project_storages/destroy_confirmation_dialog_component.html.erb
+++ b/modules/storages/app/components/storages/project_storages/destroy_confirmation_dialog_component.html.erb
@@ -44,7 +44,8 @@ See COPYRIGHT and LICENSE files for more details.
<% dialog.with_additional_details do %>
<% render(Primer::OpenProject::FlexLayout.new) do |flex| %>
<% flex.with_row do %>
- <%= t("storages.delete_warning.project_storage", file_storage: "#{h(@project_storage.storage.name)}").html_safe %>
+ <%= t("storages.delete_warning.project_storage_html",
+ file_storage: content_tag(:strong, @project_storage.storage.name)) %>
<% end %>
<% flex.with_row do %>
diff --git a/modules/storages/app/views/storages/project_settings/project_storage_members/index.html.erb b/modules/storages/app/views/storages/project_settings/project_storage_members/index.html.erb
index 8367a04d372..26814125f77 100644
--- a/modules/storages/app/views/storages/project_settings/project_storage_members/index.html.erb
+++ b/modules/storages/app/views/storages/project_settings/project_storage_members/index.html.erb
@@ -44,9 +44,9 @@ See COPYRIGHT and LICENSE files for more details.
<%= content_tag(
:p,
t(
- "storages.page_titles.project_storage_members.subtitle",
+ "storages.page_titles.project_storage_members.subtitle_html",
storage_name_link: link_to(@storage.name, edit_admin_settings_storage_path(@storage))
- ).html_safe
+ )
) %>
<%= render(::Storages::ProjectStorages::Members::TableComponent.new(rows: @project_users, storage: @storage)) %>
diff --git a/modules/storages/config/locales/en.yml b/modules/storages/config/locales/en.yml
index 49b2a4cd140..4937ebfbb72 100644
--- a/modules/storages/config/locales/en.yml
+++ b/modules/storages/config/locales/en.yml
@@ -227,13 +227,13 @@ en:
confirm_replace_oauth_application: This action will reset the current OAuth credentials. After confirming you will have to reenter the credentials at the storage provider and all remote users will have to authorize against OpenProject again. Are you sure you want to proceed?
confirm_replace_oauth_client: This action will reset the current OAuth credentials. After confirming you will have to enter new credentials from the storage provider and all users will have to authorize against %{provider_type} again. Are you sure you want to proceed?
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?
+ project_storage_html: Are you sure you want to remove %{file_storage} from this project?
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
+ storage_html: Are you sure you want to delete %{file_storage} as an external file storage?
dependencies:
nextcloud:
group_folders_app: Team Folders
@@ -413,8 +413,7 @@ en:
no_specific_folder: By default, each user will start at their own home folder when they upload a file.
no_storage_set_up: There are no file storages set up yet.
not_logged_into_storage: To select a project folder, please first login
- oauth_application_details: The client secret value will not be accessible again after you close this window. Please copy these values into the %{oauth_application_details_link}.
- oauth_application_details_link_text: Nextcloud OpenProject Integration settings
+ oauth_application_details: The client secret value will not be accessible again after you close this window. Please copy these values into the [Nextcloud OpenProject Integration settings](oauth_application_details_link).
one_drive:
application_link_text: Azure portal
copy_redirect_uri: Copy redirect URI
@@ -522,7 +521,7 @@ en:
members_connection_status: Members connection status
new: Add a file storage to this project
project_storage_members:
- subtitle: Check the connection status for the storage %{storage_name_link} of all project members.
+ subtitle_html: Check the connection status for the storage %{storage_name_link} of all project members.
title: Members connection status
project_storages:
delete: Remove file storage from project?
diff --git a/modules/two_factor_authentication/app/views/two_factor_authentication/authentication/request_otp.html.erb b/modules/two_factor_authentication/app/views/two_factor_authentication/authentication/request_otp.html.erb
index d00b885bbf2..d1ab0c2db62 100644
--- a/modules/two_factor_authentication/app/views/two_factor_authentication/authentication/request_otp.html.erb
+++ b/modules/two_factor_authentication/app/views/two_factor_authentication/authentication/request_otp.html.erb
@@ -20,7 +20,7 @@
<%= t "two_factor_authentication.label_two_factor_authentication" %>
<% identifier = @used_device.request_2fa_identifier(@strategy.channel) %>
<% if @used_device.input_based? %>
-
<%= raw I18n.t("two_factor_authentication.devices.2fa_from_input", device_name: identifier) %>
diff --git a/modules/two_factor_authentication/config/locales/en.yml b/modules/two_factor_authentication/config/locales/en.yml
index 1110c45f4f5..b27585bd76b 100644
--- a/modules/two_factor_authentication/config/locales/en.yml
+++ b/modules/two_factor_authentication/config/locales/en.yml
@@ -75,8 +75,7 @@ en:
error_invalid_settings: "The 2FA strategies you selected are invalid"
failed_to_save_settings: "Failed to update 2FA settings: %{message}"
admin:
- self_edit_path: "To add or modify your own 2FA devices, please go to %{self_edit_link}"
- self_edit_link_name: "Two-factor authentication on your account page"
+ self_edit_path: "To add or modify your own 2FA devices, please go to the [Two-factor authentication on your account page](self_edit_link)"
self_edit_forbidden: "You may not edit your own 2FA devices on this path. Go to My Account > Two factor authentication instead."
no_devices_for_user: "No 2FA device has been registered for this user."
all_devices_deleted: "All 2FA devices of this user have been deleted"
@@ -124,8 +123,8 @@ en:
failed_to_delete: "Failed to delete 2FA device."
is_default_cannot_delete: "The device is marked as default and cannot be deleted due to an active security policy. Mark another device as default before deleting."
not_existing: "No 2FA device has been registered for your account."
- 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_html: Please enter the code from your %{device_name} to verify your identity.
+ 2fa_from_webauthn_html: 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.
webauthn:
title: "WebAuthn"
description: Register a FIDO2 device (like YubiKey) or the secure enclave of your mobile device.
diff --git a/modules/webhooks/app/views/webhooks/outgoing/admin/index.html.erb b/modules/webhooks/app/views/webhooks/outgoing/admin/index.html.erb
index ba283113c9e..1079824d619 100644
--- a/modules/webhooks/app/views/webhooks/outgoing/admin/index.html.erb
+++ b/modules/webhooks/app/views/webhooks/outgoing/admin/index.html.erb
@@ -10,10 +10,10 @@
)
header.with_description do
- t(
+ link_translate(
"webhooks.outgoing.explanation.text",
- link: link_to(t(:"webhooks.outgoing.explanation.link"), admin_settings_aggregation_path, target: "_blank", rel: "noopener")
- ).html_safe
+ links: { aggregation_path: admin_settings_aggregation_path }
+ )
end
end
%>
diff --git a/modules/webhooks/config/locales/en.yml b/modules/webhooks/config/locales/en.yml
index 69ec1fd5c2a..fc9d7263f97 100644
--- a/modules/webhooks/config/locales/en.yml
+++ b/modules/webhooks/config/locales/en.yml
@@ -40,8 +40,7 @@ en:
explanation:
text: >
Upon the occurrence of an event like the creation of a work package or an update on a project, OpenProject will send a POST request to the configured web endpoints.
- Oftentimes, the event is sent after the %{link} has passed.
- link: configured aggregation period
+ Oftentimes, the event is sent after the [configured aggregation period](aggregation_path) has passed.
status:
enabled: 'Webhook is enabled'
disabled: 'Webhook is disabled'
diff --git a/spec/features/auth/omniauth_spec.rb b/spec/features/auth/omniauth_spec.rb
index 80a3c2e2b75..18d13165a57 100644
--- a/spec/features/auth/omniauth_spec.rb
+++ b/spec/features/auth/omniauth_spec.rb
@@ -62,12 +62,6 @@ RSpec.describe "Omniauth authentication" do
OmniAuth.config.logger = @omniauth_logger
end
- ##
- # Returns a given translation up until the first occurrence of a parameter (exclusive).
- def translation_substring(translation)
- translation.scan(/(^.*) %\{/).first.first
- end
-
describe "existing user sign in" do
it "redirects to back url" do
visit account_lost_password_path
@@ -121,7 +115,7 @@ RSpec.describe "Omniauth authentication" do
visit signout_path
expect(page).to have_content(I18n.t(:notice_logged_out))
- expect(page).to have_content translation_substring(I18n.t(:instructions_after_logout))
+ expect(page).to have_content I18n.t(:instructions_after_logout)
end
it "sign-in after previous sign-out shows my page" do
@@ -268,7 +262,7 @@ RSpec.describe "Omniauth authentication" do
it_behaves_like "omniauth signin error" do
let(:login_path) { signin_path }
- let(:instructions) { translation_substring I18n.t(:instructions_after_error) }
+ let(:instructions) { I18n.t(:instructions_after_error) }
end
end
end
diff --git a/spec/helpers/error_message_helper_spec.rb b/spec/helpers/error_message_helper_spec.rb
index 788a851e21b..42643d6ddba 100644
--- a/spec/helpers/error_message_helper_spec.rb
+++ b/spec/helpers/error_message_helper_spec.rb
@@ -42,7 +42,7 @@ RSpec.describe ErrorMessageHelper do
Array(attributes)
.flat_map { |attribute| errors.full_messages_for(attribute) }
.map { CGI.escapeHTML it }
- .join(" ")
+ .join(" ")
end
before do
@@ -66,7 +66,7 @@ RSpec.describe ErrorMessageHelper do
expect(description).to be_html_safe.and eq([
t("errors.header_invalid_fields", count: 1),
join_escaped_errors(:title)
- ].join(" "))
+ ].join(" "))
end
end
@@ -83,7 +83,7 @@ RSpec.describe ErrorMessageHelper do
expect(description).to be_html_safe.and eq([
t("errors.header_invalid_fields", count: 2),
join_escaped_errors(%i[title author])
- ].join(" "))
+ ].join(" "))
end
end
@@ -114,7 +114,7 @@ RSpec.describe ErrorMessageHelper do
join_escaped_errors(:base),
t("errors.header_additional_invalid_fields", count: 1),
join_escaped_errors(:title)
- ].join(" "))
+ ].join(" "))
end
end
@@ -134,7 +134,7 @@ RSpec.describe ErrorMessageHelper do
join_escaped_errors(:base),
t("errors.header_additional_invalid_fields", count: 2),
join_escaped_errors(%i[title author])
- ].join(" "))
+ ].join(" "))
end
end
end
diff --git a/spec/helpers/text_formatting_helper_spec.rb b/spec/helpers/text_formatting_helper_spec.rb
index e3efd6d59e4..870798db830 100644
--- a/spec/helpers/text_formatting_helper_spec.rb
+++ b/spec/helpers/text_formatting_helper_spec.rb
@@ -108,7 +108,7 @@ RSpec.describe TextFormattingHelper do
"Lorem ipsum dolor sit \namet, consetetur sadipscing elitr, sed diam nonumy eirmod\n tempor invidunt"
end
let(:text_html) do
- "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt"
+ "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt"
end
it "replaces escaped line breaks with html line breaks and should be html_safe" do
diff --git a/spec/lib/tabular_form_builder_spec.rb b/spec/lib/tabular_form_builder_spec.rb
index 4096ee1a6b6..010ea5e3840 100644
--- a/spec/lib/tabular_form_builder_spec.rb
+++ b/spec/lib/tabular_form_builder_spec.rb
@@ -91,7 +91,9 @@ RSpec.describe TabularFormBuilder do
end
context "with a prefix" do
- let(:options) { { title: "Name", prefix: %{Prefix} } }
+ let(:options) do
+ { title: "Name", prefix: ApplicationController.helpers.content_tag(:span, "Prefix", style: "color:red") }
+ end
it "outputs elements" do
expect(output).to be_html_eql(%{
@@ -121,7 +123,9 @@ RSpec.describe TabularFormBuilder do
end
context "with a suffix" do
- let(:options) { { title: "Name", suffix: %{Suffix} } }
+ let(:options) do
+ { title: "Name", suffix: ApplicationController.helpers.content_tag(:span, "Suffix", style: "color:blue") }
+ end
it "outputs elements" do
expect(output).to be_html_eql(%{
@@ -147,8 +151,8 @@ RSpec.describe TabularFormBuilder do
let(:options) do
{
title: "Name",
- prefix: %{PREFIX},
- suffix: %{SUFFIX}
+ prefix: ApplicationController.helpers.content_tag(:span, "PREFIX", style: "color:yellow"),
+ suffix: ApplicationController.helpers.content_tag(:span, "SUFFIX", style: "color:green")
}
end