Skip to content

Commit a9e1af5

Browse files
authored
fix: prevent html duplication in android bucket (#1647)
* fix: prevent HTML tag duplication in Android XML strings * chore: add changeset
1 parent 19a71eb commit a9e1af5

File tree

7 files changed

+57
-20
lines changed

7 files changed

+57
-20
lines changed

.changeset/stale-pandas-run.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"lingo.dev": patch
3+
---
4+
5+
prevent HTML tag duplication in Android bucket

packages/cli/demo/android/en/example.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,11 @@
9595
<item>@drawable/icon2</item>
9696
</array>
9797

98+
<string name="terms_of_use_raw"><u>Terms of Use</u></string>
99+
98100
<!-- Resource IDs - will not be translated (copied as-is to target locales) -->
99101
<item type="id" name="button_ok" />
100102

103+
<string name="view_your_options"><u>View your options</u></string>
104+
101105
</resources>

packages/cli/demo/android/es/example.xml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
<string name="html_snippet">&lt;b&gt;Negrita&lt;/b&gt;</string>
2525

26-
<string name="apostrophe_example">¡No olvides!</string>
26+
<string name="apostrophe_example">¡No lo olvides!</string>
2727

2828
<string name="cdata_example"><![CDATA[Los usuarios solo pueden ver tu comentario después de registrarse. <u>Más información.</u>]]></string>
2929

@@ -49,6 +49,10 @@
4949
<item>@drawable/icon2</item>
5050
</array>
5151

52+
<string name="terms_of_use_raw">&lt;u&gt;Términos de uso&lt;/u&gt;</string>
53+
5254
<item type="id" name="button_ok"></item>
5355

56+
<string name="view_your_options">&lt;u&gt;Ver tus opciones&lt;/u&gt;</string>
57+
5458
</resources>

packages/cli/demo/android/i18n.lock

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ checksums:
44
app_name: 7dc70110429d46e3685f385bd2cc941c
55
welcome_message: 0468579ef2fbc83c9d520c2f2f1c5059
66
button_text: 1d5f030c4ec9c869e647ae060518b948
7-
html_snippet: f060191b1af70b3848106a4df91f43cd
8-
apostrophe_example: 997099339b144b06266f8da411de8d93
9-
cdata_example: ba876d1379f854628eaebf67ea330ccc
107
color_names/0: bace0083b78cdb188523bc4abc7b55c6
118
color_names/1: 482ff383a4258357ba404f283682471d
12-
color_names/2: a5cf034b2d370a976119335cd99f4217
13-
mixed_items/0: 9278f79dfb062c6c04f6395108907816
14-
mixed_items/1: 9823a57cbe6e6e84c1d025ce24a1eec4
9+
color_names/2: a3f56a5c28ea75888356c2280aadc0e7
1510
notification_count/one: fe0aceb70f334c52a87937c36898a1d0
1611
notification_count/other: 13acfd95b16962ebe1f67dcd343513e1
12+
html_snippet: f060191b1af70b3848106a4df91f43cd
13+
apostrophe_example: 997099339b144b06266f8da411de8d93
14+
cdata_example: b88d55f92c4a90f64016e497051e997d
15+
mixed_items/0: 31c5d470a2fe8e1ae88e964fc673aee3
16+
mixed_items/1: 9823a57cbe6e6e84c1d025ce24a1eec4
17+
terms_of_use_raw: e3048f75742e66473369a83c10ea95c3

packages/cli/i18n.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,8 @@ checksums:
422422
cdata_example: b88d55f92c4a90f64016e497051e997d
423423
mixed_items/0: 31c5d470a2fe8e1ae88e964fc673aee3
424424
mixed_items/1: 9823a57cbe6e6e84c1d025ce24a1eec4
425+
terms_of_use_raw: e3048f75742e66473369a83c10ea95c3
426+
view_your_options: 416ed59ca3254f9da0d565c7c75f9033
425427
df547e152136431bbc29e26ae0eeabb4:
426428
title: 0468579ef2fbc83c9d520c2f2f1c5059
427429
description: 49f8864eb0e53903f04532bf33e1e4fa

packages/cli/src/cli/loaders/android.spec.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,34 @@ describe("android loader", () => {
9494
});
9595
});
9696

97+
it("should correctly handle HTML markup in strings during push without duplication", async () => {
98+
const input = `
99+
<resources>
100+
<string name="terms_of_use"><u>Terms of Use</u></string>
101+
<string name="welcome">Welcome to <b>Android</b>!</string>
102+
</resources>
103+
`.trim();
104+
105+
const androidLoader = createAndroidLoader().setDefaultLocale("en");
106+
107+
// Pull first to initialize loader state
108+
await androidLoader.pull("en", input);
109+
110+
// Push translated content with HTML
111+
const pushed = await androidLoader.push("es", {
112+
terms_of_use: "<u>Términos de uso</u>",
113+
welcome: "Bienvenido a <b>Android</b>!",
114+
});
115+
116+
// Verify no duplication - should only contain escaped HTML, not both escaped and unescaped
117+
expect(pushed).toContain("&lt;u&gt;Términos de uso&lt;/u&gt;");
118+
expect(pushed).not.toContain("<u>Términos de uso</u>&lt;u&gt;");
119+
expect(pushed).not.toContain("&lt;u&gt;Terms of Use&lt;/u&gt;&lt;u&gt;Términos de uso&lt;/u&gt;");
120+
121+
expect(pushed).toContain("&lt;b&gt;Android&lt;/b&gt;");
122+
expect(pushed).not.toContain("<b>Android</b>&lt;b&gt;");
123+
});
124+
97125
it("should correctly handle format strings", async () => {
98126
const input = `
99127
<resources>

packages/cli/src/cli/loaders/android.ts

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -641,19 +641,12 @@ function setTextualNodeContent(
641641
: escapeAndroidString(value);
642642
node._ = escapedValue;
643643

644-
node.$$ = node.$$ ?? [];
645-
let textNode = node.$$.find(
646-
(child: any) =>
647-
child["#name"] === "__text__" || child["#name"] === "__cdata",
648-
);
649-
650-
if (!textNode) {
651-
textNode = {};
652-
node.$$.push(textNode);
653-
}
654-
655-
textNode["#name"] = useCdata ? "__cdata" : "__text__";
656-
textNode._ = escapedValue;
644+
// Replace entire children array to avoid duplicating inline HTML elements
645+
// When inline HTML exists (e.g., <b>text</b>), xml2js creates element nodes
646+
// in node.$$ that would otherwise be serialized alongside the escaped text
647+
node.$$ = [
648+
{ "#name": useCdata ? "__cdata" : "__text__", _: escapedValue }
649+
];
657650
}
658651

659652
function buildResourceNameMap(

0 commit comments

Comments
 (0)