Skip to content

Commit f8c22c1

Browse files
authored
fix(srt): handle undefined/null text in malformed SRT files (#1986)
* fix(cli/srt): filter out undefined/null subtitle entries during push and pull operations * fix(cli/srt): add warning for skipping undefined/null subtitle entries during push
1 parent 95959d8 commit f8c22c1

File tree

3 files changed

+109
-14
lines changed

3 files changed

+109
-14
lines changed

.changeset/fix-srt-undefined.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+
fix(srt): filter undefined/null subtitle entries during push

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

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2851,6 +2851,84 @@ World!
28512851

28522852
expect(data).toEqual({ "2#00:00:01,000-00:00:02,000": "World!" });
28532853
});
2854+
2855+
it("should handle undefined text values in payload", async () => {
2856+
setupFileMocks();
2857+
2858+
const input = `
2859+
1
2860+
00:00:00,000 --> 00:00:01,000
2861+
Hello!
2862+
2863+
2
2864+
00:00:01,000 --> 00:00:02,000
2865+
World!
2866+
2867+
3
2868+
00:00:02,000 --> 00:00:03,000
2869+
Test!
2870+
`.trim();
2871+
2872+
const payload = {
2873+
"1#00:00:00,000-00:00:01,000": "¡Hola!",
2874+
"2#00:00:01,000-00:00:02,000": undefined as any,
2875+
"3#00:00:02,000-00:00:03,000": "¡Prueba!",
2876+
};
2877+
2878+
const expectedOutput = `1
2879+
00:00:00,000 --> 00:00:01,000
2880+
¡Hola!
2881+
2882+
3
2883+
00:00:02,000 --> 00:00:03,000
2884+
¡Prueba!`;
2885+
2886+
mockFileOperations(input);
2887+
2888+
const srtLoader = createBucketLoader("srt", "i18n/[locale].srt", {
2889+
defaultLocale: "en",
2890+
});
2891+
srtLoader.setDefaultLocale("en");
2892+
await srtLoader.pull("en");
2893+
2894+
await srtLoader.push("es", payload);
2895+
2896+
expect(fs.writeFile).toHaveBeenCalledWith("i18n/es.srt", expectedOutput, {
2897+
encoding: "utf-8",
2898+
flag: "w",
2899+
});
2900+
});
2901+
2902+
it("should handle malformed SRT entries with undefined text during pull", async () => {
2903+
setupFileMocks();
2904+
2905+
// Simulating malformed SRT that causes parser to return undefined text
2906+
const malformedInput = `1
2907+
00:00:00,000 --> 00:00:01,000
2908+
Hello!
2909+
2910+
2
2911+
00:00:01,000 -- 00:00:02,000
2912+
World!
2913+
2914+
3
2915+
00:00:02,000 --> 00:00:03,000
2916+
Test!`;
2917+
2918+
mockFileOperations(malformedInput);
2919+
2920+
const srtLoader = createBucketLoader("srt", "i18n/[locale].srt", {
2921+
defaultLocale: "en",
2922+
});
2923+
srtLoader.setDefaultLocale("en");
2924+
const data = await srtLoader.pull("en");
2925+
2926+
// Should only include entries with valid text, skipping malformed entry #2
2927+
expect(data).toHaveProperty("1#00:00:00,000-00:00:01,000");
2928+
expect(data).toHaveProperty("3#00:00:02,000-00:00:03,000");
2929+
// Entry #2 should be filtered out if parser returns undefined text
2930+
expect(data).not.toHaveProperty("2#00:00:01,000-00:00:02,000");
2931+
});
28542932
});
28552933

28562934
describe("xliff bucket loader", () => {

packages/cli/src/cli/loaders/srt.ts

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,39 @@ export default function createSrtLoader(): ILoader<
1313
const result: Record<string, string> = {};
1414

1515
parsed.forEach((entry) => {
16-
const key = `${entry.id}#${entry.startTime}-${entry.endTime}`;
17-
result[key] = entry.text;
16+
if (entry.text !== undefined && entry.text !== null) {
17+
const key = `${entry.id}#${entry.startTime}-${entry.endTime}`;
18+
result[key] = entry.text;
19+
}
1820
});
1921

2022
return result;
2123
},
2224

2325
async push(locale, payload) {
24-
const output = Object.entries(payload).map(([key, text]) => {
25-
const [id, timeRange] = key.split("#");
26-
const [startTime, endTime] = timeRange.split("-");
26+
const output = Object.entries(payload)
27+
.filter(([key, text]) => {
28+
if (text === undefined || text === null) {
29+
console.warn(
30+
`⚠️ [SRT] Skipping subtitle entry ${key} - text is ${text === undefined ? "undefined" : "null"}`,
31+
);
32+
return false;
33+
}
34+
return true;
35+
})
36+
.map(([key, text]) => {
37+
const [id, timeRange] = key.split("#");
38+
const [startTime, endTime] = timeRange.split("-");
2739

28-
return {
29-
id: id,
30-
startTime: startTime,
31-
startSeconds: 0,
32-
endTime: endTime,
33-
endSeconds: 0,
34-
text: text,
35-
};
36-
});
40+
return {
41+
id: id,
42+
startTime: startTime,
43+
startSeconds: 0,
44+
endTime: endTime,
45+
endSeconds: 0,
46+
text: text,
47+
};
48+
});
3749

3850
const srtContent = parser.toSrt(output).trim().replace(/\r?\n/g, "\n");
3951
return srtContent;

0 commit comments

Comments
 (0)