Skip to content

Lint _html keys in YML files#8713

Merged
zachmargolis merged 14 commits intomainfrom
margolis-lint-html-yml
Jul 6, 2023
Merged

Lint _html keys in YML files#8713
zachmargolis merged 14 commits intomainfrom
margolis-lint-html-yml

Conversation

@zachmargolis
Copy link
Contributor

Follow-up to #8708 (comment)

I am trying to see what it would take to enforce:

  1. that all i18n keys that have HTML inside them have a _html suffix
  2. that all keys with an _html suffix actually contain HTML that needs escaping

@zachmargolis zachmargolis requested review from aduth and jmdembe July 3, 2023 18:28
Comment on lines 90 to 99
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The copy for was like Some sentence. %{cancel_link} so I figured might as well split this out into two separate i18n keys

@aduth aduth changed the title Link _html keys in YML files Lint _html keys in YML files Jul 5, 2023
* Makes sure they actually contain HTML

changelog: Internal, Source code, Make HTML escaping in translations consistent
@zachmargolis zachmargolis force-pushed the margolis-lint-html-yml branch from afe3876 to dc13fd9 Compare July 5, 2023 20:55
@zachmargolis zachmargolis marked this pull request as ready for review July 5, 2023 20:56
@zachmargolis
Copy link
Contributor Author

I think I got this working and cleaned up the whole codebase! PTAL!

<p><%= t('notices.forgot_password.no_email_sent_explanation_start') %>
<%= f.button :button, t('links.resend'), class: 'usa-button--unstyled margin-left-05' %></p>

<% link = link_to(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I chose to inline this assignment


<%= render PageHeadingComponent.new.with_content(t('forms.verify_profile.welcome_back')) %>
<%= sanitize t('forms.verify_profile.welcome_back_description'), tags: %w[p strong] %>
<%= t('forms.verify_profile.welcome_back_description_html') %>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the _html does the same marking as HTML-safe that sanitize does basically

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the sanitize would have been stricter about the allowed tags? But I don't think we worry about that with non-user-provided strings like this, or at least we don't elsewhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah with hard coded string I don't think we gain much benefit from sanitize

Comment on lines -23 to +24
<%= raw t(
'.help', disavowal_link: link_to(
<%= t(
'.help_html', disavowal_link_html: link_to(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto here, switching to an _html key is the same as raw

<%= t('notices.signed_up_and_confirmed.no_email_sent_explanation_start') %>
<%= button_to(add_email_resend_path, method: :post, class: 'usa-button usa-button--unstyled', form_class: 'display-inline-block padding-left-1') { t('links.resend') } %>

<p><% link = link_to(t('notices.use_diff_email.link'), add_email_path) %></p>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was an empty <p> tag because <% doesn't output anything, so I removed it when inlining the variable

de sauvegarde en lieu sûr. Si vous perdez vos codes de sauvegarde, vous
ne pourrez plus vous connecter à %{app_name}.
new_backup_codes_html: J’ai besoin de nouveaux codes de sauvegarde
new_backup_codes_html: J’ai besoin de nouveaux codes de sauvegarde&ZeroWidthSpace;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok so for this one, the English copy has &nbsp; to control word wrapping, but I didn't know the appropriate place to put one in the other languages, so I added &ZeroWidthSpace; to the end because it should be invisible and not affect the wrapping

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's also the specific string from #8708

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Saw this comment after my previous remark)

Copy link
Contributor

@aduth aduth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love this 👍

t(
"instructions.mfa.#{otp_delivery_preference}.number_message_html",
number: content_tag(:strong, phone_number),
number_html: content_tag(:strong, phone_number),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to clarify: Does the automated test catch that this should be _html-suffixed, or was it manual? Since the HTML only comes at the runtime code via content_tag

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or I suppose you were able to work your way backward on the parent string (e.g. number_message_html) that was otherwise flagged for an unnecessary _html to see why it was in-fact necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct the parent tag had an un-necessary HTML without that


<%= render PageHeadingComponent.new.with_content(t('forms.verify_profile.welcome_back')) %>
<%= sanitize t('forms.verify_profile.welcome_back_description'), tags: %w[p strong] %>
<%= t('forms.verify_profile.welcome_back_description_html') %>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the sanitize would have been stricter about the allowed tags? But I don't think we worry about that with non-user-provided strings like this, or at least we don't elsewhere.

respaldo. No podrá iniciar sesión en %{app_name} si pierde sus códigos
de respaldo.
new_backup_codes_html: Necesito nuevos códigos de respaldo
new_backup_codes_html: Necesito nuevos códigos de respaldo&ZeroWidthSpace;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was the &ZeroWidthSpace; added here to avoid having it be flagged?

I wonder if we should only check that at least one of the locales has HTML?

Alternatively, the idea with the &nbsp; in the English version is to keep "backup codes" together on the same line, and I suppose we could do something similar in other languages like códigos&nbsp;de&nbsp;respaldo

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you think that's a better way to prevent wrapping, I'm all for it? I didn't have a sense of the font size/layout of this page

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about the idea of the new test only running over the en.yml file, or just checking that at least one of the locales has HTML? So that we don't need to change this file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea of checking that "at least one" has HTML... but the current structure of the assertions is each file independently, not grouped by locale. Let me noodle on that

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gave it a shot in a300659, PTAL!

I futzed with it, so a failure where one string has HTML but none of them should looks like this now:

Failures:

  1) I18n config/locales/two_factor_authentication has HTML inside at least one locale string for all keys with .html or _html 
     Failure/Error: expect(bad_keys).to be_empty
       expected `{"two_factor_authentication.account_reset.cancel_link"=>{"en"=>"Cancel your request", "es"=>"Cancelar su solicitud <STRONG>", "fr"=>"Annuler votre demande"}}.empty?` to be truthy, got false

de sauvegarde en lieu sûr. Si vous perdez vos codes de sauvegarde, vous
ne pourrez plus vous connecter à %{app_name}.
new_backup_codes_html: J’ai besoin de nouveaux codes de sauvegarde
new_backup_codes_html: J’ai besoin de nouveaux codes de sauvegarde&ZeroWidthSpace;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Saw this comment after my previous remark)

Copy link
Contributor

@Sgtpluck Sgtpluck left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sweet!

t('instructions.mfa.piv_cac.no_certificate_html', try_again_html: try_again_link)
when 'certificate.not_auth_cert'
t('instructions.mfa.piv_cac.not_auth_cert_html', please_try_again: please_try_again_link)
t('instructions.mfa.piv_cac.not_auth_cert_html', please_try_again_html: please_try_again_link)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[question, nonblocking] why are we sometimes saying please and sometimes not?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you asking about the specific copy? I'm not sure. Might be personal differences in how different engineers turn strings into keys

Copy link
Contributor

@aduth aduth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

@zachmargolis zachmargolis merged commit 6c75bb9 into main Jul 6, 2023
@zachmargolis zachmargolis deleted the margolis-lint-html-yml branch July 6, 2023 18:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants