Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[localize] Make placeholder validation pass with different expressions #4530

Merged
merged 7 commits into from
Jan 31, 2024

Conversation

augustjk
Copy link
Member

Fixes #4502

How it used to be before #2405

In the past, the expressions in the translated message placeholder were directly used in generating the output code in transform mode.

foo.ts (source)
msg(str`Hello ${user}!`);
es-419.xlf (translation)
<trans-unit id="s00ad08ebae1e0f74">
  <source>Hello <x id="0" equiv-text="${user}"/>!</source>
  <target>Hola <x id="0" equiv-text="${user}"/>!</target>
</trans-unit>

used the actual equiv-text="${user}" value from <target> to generate

foo.js (output)
`Hello ${user}!`;

This meant it was possible for faulty or malicious JS expression to be injected to the output if translation placeholders were modified.

es-419.xlf (translation)
<trans-unit id="s00ad08ebae1e0f74">
  <source>Hello <x id="0" equiv-text="${user}"/>!</source>
  <target>Hola <x id="0" equiv-text="${alert('evil') || user}"/>!</target>
</trans-unit>

could produce

foo.js (output)
`Hola ${alert('evil') || user}!`;

Runtime mode never had this issue since expressions are replaced by numbers in the output anyway and swap them with values given during runtime.

es-419.ts (runtime mode output)
export const templates = {
  s00ad08ebae1e0f74: str`Hola ${0}!`,
}

After #2405

#2405 made it so that multiple msg() calls with different expressions can map to the same translation unit.

foo.ts (source)
msg(str`Hello ${user}!`);
msg(str`Hello ${username}!`);

resulted in a single translation unit.

es-419.xlf (translation)
<trans-unit id="s00ad08ebae1e0f74">
  <source>Hello <x id="0" equiv-text="${user}"/>!</source>
  <target>Hola <x id="0" equiv-text="${user}"/>!</target>
</trans-unit>

The value of equiv-text is based on which expression is encountered first.

As such, it also made it so that we never use the expression from translation directly instead always grab the expression from the source code so that we'll produce the appropriate output (both ${user} and ${username} in above example). This means strict validation of expressions in placeholders are now unnecessary.

However, there remained some validation logic that would check that expressions in placeholders exactly as they are in the source code. If the source code were modified or the order of the template encountered changes while placeholders in the translations are not updated, errors like the follow would show up during build, even though the translations are still perfectly valid to use.

Placeholder error in es-419 localization of s00ad08ebae1e0f74: unexpected "${username}"
Placeholder error in es-419 localization of s00ad08ebae1e0f74: missing "${user}"

What this PR does

The validation logic is now updated to replace all expressions to "expr" before comparison so a different expression wouldn't cause it to fail. We still want to make sure the rest of the string matches since placeholders can also include HTML that won't be translated, and placeholders without expressions are still used in output generation for runtime mode.

Copy link

changeset-bot bot commented Jan 30, 2024

🦋 Changeset detected

Latest commit: ceb1001

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@lit/localize-tools Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Contributor

github-actions bot commented Jan 30, 2024

📊 Tachometer Benchmark Results

Summary

nop-update

  • this-change, tip-of-tree, previous-release: unsure 🔍 -6% - +6% (-0.68ms - +0.67ms)
    this-change vs tip-of-tree

render

  • this-change: 47.96ms - 50.04ms
  • this-change, tip-of-tree, previous-release: unsure 🔍 -2% - +4% (-0.43ms - +0.77ms)
    this-change vs tip-of-tree
  • this-change, tip-of-tree, previous-release: unsure 🔍 -2% - +2% (-0.57ms - +0.69ms)
    this-change vs tip-of-tree
  • this-change, tip-of-tree, previous-release: faster ✔ 0% - 4% (0.12ms - 1.32ms)
    this-change vs tip-of-tree

update

  • this-change: 493.13ms - 496.65ms
  • this-change, tip-of-tree, previous-release: unsure 🔍 -8% - +5% (-3.44ms - +1.88ms)
    this-change vs tip-of-tree
  • this-change, tip-of-tree, previous-release: unsure 🔍 -2% - +1% (-1.20ms - +0.79ms)
    this-change vs tip-of-tree
  • this-change, tip-of-tree, previous-release: faster ✔ 0% - 1% (0.39ms - 6.46ms)
    this-change vs tip-of-tree

update-reflect

  • this-change: 495.85ms - 498.40ms
  • this-change, tip-of-tree, previous-release: unsure 🔍 -1% - +0% (-4.70ms - +0.49ms)
    this-change vs tip-of-tree

Results

this-change

render

VersionAvg timevs
47.96ms - 50.04ms-

update

VersionAvg timevs
493.13ms - 496.65ms-

update-reflect

VersionAvg timevs
495.85ms - 498.40ms-
this-change, tip-of-tree, previous-release

render

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
18.26ms - 19.18ms-unsure 🔍
-2% - +4%
-0.43ms - +0.77ms
unsure 🔍
-8% - +3%
-1.46ms - +0.54ms
tip-of-tree
tip-of-tree
18.16ms - 18.94msunsure 🔍
-4% - +2%
-0.77ms - +0.43ms
-unsure 🔍
-8% - +2%
-1.60ms - +0.34ms
previous-release
previous-release
18.29ms - 20.07msunsure 🔍
-3% - +8%
-0.54ms - +1.46ms
unsure 🔍
-2% - +9%
-0.34ms - +1.60ms
-

update

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
38.54ms - 41.95ms-unsure 🔍
-8% - +5%
-3.44ms - +1.88ms
unsure 🔍
-4% - +8%
-1.57ms - +3.08ms
tip-of-tree
tip-of-tree
38.98ms - 43.06msunsure 🔍
-5% - +9%
-1.88ms - +3.44ms
-unsure 🔍
-3% - +11%
-1.05ms - +4.11ms
previous-release
previous-release
37.91ms - 41.07msunsure 🔍
-8% - +4%
-3.08ms - +1.57ms
unsure 🔍
-10% - +2%
-4.11ms - +1.05ms
-

nop-update

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
11.24ms - 11.88ms-unsure 🔍
-6% - +6%
-0.68ms - +0.67ms
unsure 🔍
-2% - +6%
-0.24ms - +0.72ms
tip-of-tree
tip-of-tree
10.97ms - 12.16msunsure 🔍
-6% - +6%
-0.67ms - +0.68ms
-unsure 🔍
-4% - +8%
-0.45ms - +0.93ms
previous-release
previous-release
10.97ms - 11.67msunsure 🔍
-6% - +2%
-0.72ms - +0.24ms
unsure 🔍
-8% - +4%
-0.93ms - +0.45ms
-
this-change, tip-of-tree, previous-release

render

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
33.03ms - 33.94ms-unsure 🔍
-2% - +2%
-0.57ms - +0.69ms
unsure 🔍
-2% - +1%
-0.79ms - +0.49ms
tip-of-tree
tip-of-tree
32.99ms - 33.86msunsure 🔍
-2% - +2%
-0.69ms - +0.57ms
-unsure 🔍
-2% - +1%
-0.84ms - +0.43ms
previous-release
previous-release
33.18ms - 34.09msunsure 🔍
-1% - +2%
-0.49ms - +0.79ms
unsure 🔍
-1% - +3%
-0.43ms - +0.84ms
-

update

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
66.77ms - 67.80ms-unsure 🔍
-2% - +1%
-1.20ms - +0.79ms
unsure 🔍
-1% - +1%
-0.58ms - +0.76ms
tip-of-tree
tip-of-tree
66.64ms - 68.34msunsure 🔍
-1% - +2%
-0.79ms - +1.20ms
-unsure 🔍
-1% - +2%
-0.66ms - +1.25ms
previous-release
previous-release
66.76ms - 67.62msunsure 🔍
-1% - +1%
-0.76ms - +0.58ms
unsure 🔍
-2% - +1%
-1.25ms - +0.66ms
-
this-change, tip-of-tree, previous-release

render

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
32.37ms - 33.10ms-faster ✔
0% - 4%
0.12ms - 1.32ms
unsure 🔍
-3% - +0%
-1.11ms - +0.06ms
tip-of-tree
tip-of-tree
32.97ms - 33.93msslower ❌
0% - 4%
0.12ms - 1.32ms
-unsure 🔍
-1% - +3%
-0.47ms - +0.85ms
previous-release
previous-release
32.80ms - 33.72msunsure 🔍
-0% - +3%
-0.06ms - +1.11ms
unsure 🔍
-3% - +1%
-0.85ms - +0.47ms
-

update

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
496.94ms - 499.40ms-faster ✔
0% - 1%
0.39ms - 6.46ms
unsure 🔍
-1% - +0%
-2.60ms - +0.68ms
tip-of-tree
tip-of-tree
498.82ms - 504.37msslower ❌
0% - 1%
0.39ms - 6.46ms
-unsure 🔍
-0% - +1%
-0.52ms - +5.44ms
previous-release
previous-release
498.05ms - 500.22msunsure 🔍
-0% - +1%
-0.68ms - +2.60ms
unsure 🔍
-1% - +0%
-5.44ms - +0.52ms
-

update-reflect

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
505.43ms - 508.76ms-unsure 🔍
-1% - +0%
-4.70ms - +0.49ms
unsure 🔍
-1% - +1%
-3.79ms - +2.58ms
tip-of-tree
tip-of-tree
507.21ms - 511.19msunsure 🔍
-0% - +1%
-0.49ms - +4.70ms
-unsure 🔍
-0% - +1%
-1.87ms - +4.86ms
previous-release
previous-release
504.99ms - 510.42msunsure 🔍
-1% - +1%
-2.58ms - +3.79ms
unsure 🔍
-1% - +0%
-4.86ms - +1.87ms
-

tachometer-reporter-action v2 for Benchmarks

Copy link
Contributor

The size of lit-html.js and lit-core.min.js are as expected.

Copy link
Contributor

@AndrewJakubowicz AndrewJakubowicz left a comment

Choose a reason for hiding this comment

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

AMAZING!

Comment on lines -17 to -18
Placeholder error in es-419 localization of changed-expression: unexpected "\${alert("evil") || name}"
Placeholder error in es-419 localization of changed-expression: missing "\${name}"
Copy link
Member Author

Choose a reason for hiding this comment

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

changed expressions are no longer considered placeholder errors.

@@ -57,6 +57,9 @@ html`&lt;Hola<b>&lt;Mundo &amp; Amigos&gt;</b>!&gt;`;
// Expressions as attribute values should stay as expressions
html`Hello <b foo=${'World'}>World</b>`;
html`Hello <b foo=${`Mundo`}>World</b>`;
html`<b foo=${'Hello'}>Hello</b><b bar=${`Mundo`}>World</b>`;
html`<b foo=${`Hola`}>Hello</b><b bar=${`Mundo`}>World</b>`;
Copy link
Member Author

Choose a reason for hiding this comment

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

This diff is technically unrelated to the changes in the PR but just due to a missing translation target that I noticed from the test file.

Comment on lines +49 to +52
<trans-unit id="s63f0bfacf2c00f6b">
<source>Hello</source>
<target>Hola</target>
</trans-unit>
Copy link
Member Author

Choose a reason for hiding this comment

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

This is the missing translation target I found, mentioned above.

Comment on lines +159 to +164
const normalizedPlaceholder = normalizeExpressionInTemplateString(
content.untranslatable
);
const index = remainingProgramPlaceholders.findIndex(
({normalized}) => normalized === normalizedPlaceholder
);
Copy link
Contributor

Choose a reason for hiding this comment

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

nice!

Copy link
Contributor

@AndrewJakubowicz AndrewJakubowicz left a comment

Choose a reason for hiding this comment

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

👏 👏 👏

@augustjk augustjk merged commit 258142d into main Jan 31, 2024
9 checks passed
@augustjk augustjk deleted the localize-dupe-id-validation branch January 31, 2024 17:52
@lit-robot lit-robot mentioned this pull request Jan 31, 2024
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.

[localize-tools] Validation of existing translation should not fail on changed expression
2 participants