Skip to content

Commit 1304e3e

Browse files
authored
fix[smartling]: duplicate content entries handling (#4169)
## Description If we make changes to a content entry and re-add to existing job, it was adding it twice _Screenshot_ If relevant, add a screenshot or two of the changes you made. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Prevents duplicate content when re-adding to Smartling jobs, adds localization support for `Core:Button` text, updates Smartling strings URL, and bumps versions. > > - **Smartling plugin**: > - **Duplicate handling**: `updateLocalJob` filters out content already in the job and only processes symbols for newly added items. > - **Smartling strings link**: Updates URL to filter by file URI (`content.id`) to show strings across jobs. > - **Versions/Deps**: Bump to `0.0.23-9`; pin `@builder.io/utils` to `1.1.25`. > - **Utils (`translation-helpers`)**: > - **Core:Button localization**: Treats `Core:Button` `options.text` like `Text`—exposes in `getTranslateableFields` and writes localized values in `applyTranslation`. > - **Tests**: Add cases for buttons with plain and pre-localized text. > - **Versions/Config**: Bump to `1.1.25`; tsconfig adds empty `types` array. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 4c03217. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 1b1b76e commit 1304e3e

File tree

9 files changed

+112
-21
lines changed

9 files changed

+112
-21
lines changed

packages/utils/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/utils/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@builder.io/utils",
3-
"version": "1.1.24",
3+
"version": "1.1.25",
44
"description": "Utils for working with Builder.io content",
55
"main": "./dist/index.js",
66
"scripts": {

packages/utils/src/translation-helpers.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,38 @@ test('getTranslateableFields from content to match snapshot', async () => {
5454
},
5555
},
5656
},
57+
{
58+
meta: {
59+
instructions: 'Button with plain text',
60+
},
61+
id: 'button-plain-text-id',
62+
'@type': '@builder.io/sdk:Element',
63+
component: {
64+
name: 'Core:Button',
65+
options: {
66+
text: 'Cute Baby',
67+
openLinkInNewTab: false,
68+
},
69+
},
70+
},
71+
{
72+
meta: {
73+
instructions: 'Button with pre-localized text',
74+
},
75+
id: 'button-localized-text-id',
76+
'@type': '@builder.io/sdk:Element',
77+
component: {
78+
name: 'Core:Button',
79+
options: {
80+
text: {
81+
'@type': localizedType,
82+
'en-US': 'Click Me!',
83+
Default: 'Click Here',
84+
},
85+
openLinkInNewTab: true,
86+
},
87+
},
88+
},
5789
{
5890
'@type': '@builder.io/sdk:Element',
5991
id: 'builder-custom-component-id',

packages/utils/src/translation-helpers.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,21 @@ export function getTranslateableFields(
190190
};
191191
}
192192

193+
if (el && el.id && el.component?.name === 'Core:Button' && !el.meta?.excludeFromTranslation) {
194+
const componentText = el.component.options?.text;
195+
if (componentText) {
196+
const textValue = typeof componentText === 'string'
197+
? componentText
198+
: componentText?.[sourceLocaleId] || componentText?.Default;
199+
if (textValue) {
200+
results[`blocks.${el.id}#text`] = {
201+
value: textValue,
202+
instructions: el.meta?.instructions || defaultInstructions,
203+
};
204+
}
205+
}
206+
}
207+
193208
if (el && el.id && el.component?.name === 'Symbol') {
194209
const symbolInputs = Object.entries(el.component?.options?.symbol?.data) || [];
195210
if (symbolInputs.length) {
@@ -325,6 +340,45 @@ export function applyTranslation(
325340
});
326341
}
327342

343+
// Core:Button special handling - similar to Text component
344+
if (
345+
el &&
346+
el.id &&
347+
el.component?.name === 'Core:Button' &&
348+
!el.meta?.excludeFromTranslation &&
349+
translation[`blocks.${el.id}#text`]
350+
) {
351+
const localizedValues =
352+
typeof el.component.options?.text === 'string'
353+
? {
354+
Default: el.component.options.text,
355+
}
356+
: el.component.options.text;
357+
358+
const updatedElement = {
359+
...el,
360+
meta: {
361+
...el.meta,
362+
translated: true,
363+
// this tells the editor that this is a forced localized input similar to clicking the globe icon
364+
'transformed.text': 'localized',
365+
},
366+
component: {
367+
...el.component,
368+
options: {
369+
...el.component.options,
370+
text: {
371+
'@type': localizedType,
372+
...localizedValues,
373+
[locale]: unescapeStringOrObject(translation[`blocks.${el.id}#text`].value),
374+
},
375+
},
376+
},
377+
};
378+
379+
this.update(updatedElement);
380+
}
381+
328382
// custom components
329383
if (el && el.id && el.meta?.localizedTextInputs) {
330384
// there's a localized input

packages/utils/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"esModuleInterop": true,
88
"skipLibCheck": true,
99
"strict": true,
10-
"declaration": true
10+
"declaration": true,
11+
"types": []
1112
},
1213
"include": ["./src"],
1314
"exclude": ["node_modules", "**/*.test.ts"]

plugins/smartling/package-lock.json

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

plugins/smartling/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@builder.io/plugin-smartling",
3-
"version": "0.0.23-8",
3+
"version": "0.0.23-9",
44
"description": "",
55
"keywords": [],
66
"main": "dist/plugin.system.js",
@@ -125,7 +125,7 @@
125125
"typescript": "^3.0.3"
126126
},
127127
"dependencies": {
128-
"@builder.io/utils": "^1.1.23",
128+
"@builder.io/utils": "1.1.25",
129129
"fast-json-stable-stringify": "^2.1.0",
130130
"lodash": "^4.17.21",
131131
"object-hash": "^3.0.0",

plugins/smartling/src/plugin.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -692,8 +692,8 @@ const initializeSmartlingPlugin = async () => {
692692
},
693693
async onClick(content) {
694694
const translationBatch = fastClone(content.meta).translationBatch;
695-
// https://dashboard.smartling.com/app/projects/0e6193784/strings/jobs/schqxtpcnxix
696-
const smartlingFile = `https://dashboard.smartling.com/app/projects/${translationBatch.projectId}/strings/jobs/${translationBatch.translationJobUid}`;
695+
// Filter by file URI (content ID) to show all translations across all jobs
696+
const smartlingFile = `https://dashboard.smartling.com/app/projects/${translationBatch.projectId}/strings/?urlsFilter.urls=${content.id}&limit=200&offset=0`;
697697
window.open(smartlingFile, '_blank', 'noreferrer,noopener');
698698
},
699699
});

plugins/smartling/src/smartling.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,14 +109,18 @@ export class SmartlingApi {
109109
}
110110
async updateLocalJob(jobId: string, content: any[]) {
111111
const latestDraft = await appState.getLatestDraft(jobId);
112-
const allContent = [...content];
113112
const processedSymbols = new Set<string>(); // Avoid duplicates
114113

115-
// Get existing entries to check for duplicate symbols
114+
// Get existing entries to check for duplicate content and symbols
116115
const existingEntryIds = new Set((latestDraft.data.entries || []).map((entry: any) => entry.content?.id));
117116

117+
// Filter out content that already exists in the job
118+
const newContent = content.filter(c => !existingEntryIds.has(c.id));
119+
120+
const allContent = [...newContent];
121+
118122
// Extract and include symbol content for updates too
119-
for (const contentItem of content) {
123+
for (const contentItem of newContent) {
120124
try {
121125
// Fetch the full content to analyze for symbols
122126
const fullContent = await fetch(
@@ -159,7 +163,7 @@ export class SmartlingApi {
159163
published: 'draft',
160164
};
161165

162-
const symbolCount = allContent.length - content.length;
166+
const symbolCount = allContent.length - newContent.length;
163167
if (symbolCount > 0) {
164168
} else {
165169
}

0 commit comments

Comments
 (0)