Skip to content

fix[smartling][utils]: ENG-13890 exclude URL, image and link fields from translation jobs - #4852

Merged
AishwaryaParab merged 4 commits into
mainfrom
ENG-13890_exclude_url_asset_fields
Sep 11, 2026
Merged

AishwaryaParab merged 4 commits into
mainfrom
ENG-13890_exclude_url_asset_fields

Conversation

@AishwaryaParab

@AishwaryaParab AishwaryaParab commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Description

Image links, button URLs and page links from Builder content were all showing up as strings for Smartling to translate.

Root Cause:
This is a regression from #4651 (June). That PR taught the extractor to look inside a localized list or object and send each string it finds as its own translation unit, which was the right fix for text fields that were previously being skipped.

The catch is that the extractor has no idea what type a field is. So once it starts walking a payload, it picks up every string in there. imageUrl, link and screenImage are strings, so off they went.

We already patched one version of this in #4826, where the same walk was sending dropdown values like "White" and "Left". That fix relied on the editor recording which fields are non-translatable, which only helps for localized list fields on blocks that carry that metadata. URLs on symbols and on model fields go through different code paths and were never covered.

Fix:
Added one small check: a string is not translatable if it has no whitespace and starts with /, http://, https:// or cdn.builder.io/. That check now runs everywhere a string becomes a translation unit (7 places: symbol inputs, model fields, the two nested-string branches, the localized-leaf branch, and the block flat-string case).

The whitespace part matters. "Visit https://sumup.com for more" is a real sentence and still gets translated. Only a bare URL on its own gets skipped.

Skipping a field means Smartling never returns a value for it, and the SDK resolves a missing locale to undefined rather than falling back to Default, so the link would just vanish on the translated page. To prevent that, the source URL is now copied into the target locale for any field we skip. Three changes for that: the block path already had this logic behind a condition that was too narrow, so the condition was widened; the symbol and model-field paths had none, so each got a small block.

Both new blocks are gated on the same URL check, so they only ever restore a value we deliberately skipped. A field that's still waiting on a pending job is left alone, so English never leaks into a translated locale.

Link to JIRA ticket (if applicable):
https://builder-io.atlassian.net/browse/ENG-13890

Screenshot/Clip
https://clips.agent-native.com/r/uFQLIO0vOcIM

@changeset-bot

changeset-bot Bot commented Sep 10, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 02c227a

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@builder-io-integration builder-io-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Builder reviewed your changes and found 4 potential issues 🟡

Review Details

Code Review Summary

PR #4852 updates the Smartling extraction helpers so bare route URLs, HTTP(S) URLs, and Builder CDN asset references are not emitted as translation units, while prose containing URLs remains translatable. It also attempts to seed skipped values into the target locale so links and assets do not disappear when the SDK cannot fall back to Default. The focused regression tests cover scalar URLs, nested carousel values, symbol inputs, prose, pending jobs, and existing target values.

This is a standard-risk shared utility change: the approach is directionally sound, but the new fallback behavior does not cover all nested payload shapes and has source-locale consistency gaps. Key findings:

  • 🟡 MEDIUM URL-only nested object/array payloads can disappear because only scalar defaults are restored.
  • 🟡 MEDIUM Nested URL-only localized leaves can be mistaken for pending translatable content, suppressing source seeding.
  • 🟡 MEDIUM Skipped values are restored from Default rather than the explicitly selected source locale.
  • 🟡 MEDIUM Common non-HTTP link forms such as mailto:, tel:, sms:, and fragment links remain translatable.

The added tests are useful, especially the whitespace distinction and pending-job assertions, but they do not exercise URL-only nested payloads or non-Default source locales.

🧪 Browser testing: Skipped — PR only modifies backend/shared translation utility code and tests, with no direct user-facing UI changes.

} else {
// No direct translation - check if Default value contains nested LocalizedValues
const defaultValue = value?.Default;
// Restore a leaf the extractor skipped: the SDK resolves a missing locale to

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Seed URL-only nested payloads into the target locale

The URL filter can remove every leaf from a localized object or array, but this restore path only handles a scalar Default string. For URL-only nested metadata or Symbol payloads, recursion into Default never creates the outer [locale] branch, so the SDK resolves the whole value to undefined in the translated locale and the content disappears. Seed the source payload when there are no actual translatable leaves, while preserving the pending-job behavior for payloads that do contain translatable leaves.

Fix in Builder

Comment on lines 924 to +927
if (!compoundKeys.length) {
// Everything came back excluded, or there was never anything to send. Both differ
// from the provider not having answered yet, which must leave the locale alone.
if (matchingKeys.length || (excludedPaths && !hasTranslatableLeaf())) {
if (matchingKeys.length || !hasTranslatableLeaf()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Distinguish skipped URL leaves from pending translations

extractLocalizedLeaves returns a nonzero count for a localized leaf even when its string was filtered by isNonTranslatableValue, so hasTranslatableLeaf() can return true for a payload containing only nested URLs/assets. That skips seedSourceIntoLocale() and leaves the target locale undefined. The check needs to distinguish marked-but-skipped leaves from actual translation units.

Fix in Builder

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not valid.

Comment thread packages/utils/src/translation-helpers.ts Outdated
Comment thread packages/utils/src/translation-helpers.ts Outdated
@nx-cloud

nx-cloud Bot commented Sep 10, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 02c227a

Command Status Duration Result
nx test @snippet/react ✅ Succeeded 1m 1s View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-09-11 14:30:05 UTC

builder-io-integration[bot]

This comment was marked as outdated.

builder-io-integration[bot]

This comment was marked as outdated.

@AishwaryaParab
AishwaryaParab requested review from a team and lihuelg and removed request for a team September 11, 2026 10:25

// A bare URL or asset reference is a routing/config value, never a translation unit.
// Whitespace means prose ("Visit https://x.com for more"), which stays translatable.
function isNonTranslatableValue(value: string) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

does this mean url is not translatable, but a text containing an url is?
Couldn't it be solved using a new URL()?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

does this mean url is not translatable, but a text containing an url is? -> that's correct
I did explore new URL() but SumUp also uses relative paths like /en-gb/pricing, ./checkout, cdn.builder.io/api/v1/image/x and just #, so, new URL() doesn't really cover these

@builder-io-integration builder-io-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Builder reviewed your changes — no new findings

Review Details

Incremental Code Review Summary

The latest commit adds this.block() after handling a localized model payload, preventing traversal from descending into the original Default subtree and mutating nested source values. I verified that this addresses the previously reported Default-payload mutation issue and resolved that review thread. The URL predicate and model seeding changes remain otherwise consistent with the prior review.

No new actionable issues were identified in this increment. The previously reported nested Symbol seeding and skipped-leaf/pending-translation issues remain unresolved and were intentionally not reposted.

This is a standard-risk shared translation utility change.

🧪 Browser testing: Skipped — the PR modifies shared translation utilities and tests, with no direct user-facing UI changes.

@AishwaryaParab
AishwaryaParab merged commit 456c70a into main Sep 11, 2026
180 of 186 checks passed
@AishwaryaParab
AishwaryaParab deleted the ENG-13890_exclude_url_asset_fields branch September 11, 2026 14:30
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.

2 participants