Skip to content

Commit 80bcbe4

Browse files
authored
feat: yaml root key preserve formatting (#1640)
* feat: preserve formatting in yaml files * feat: preserve mixed key quoting * fix: yaml-room-key detection * chore: add changeset
1 parent 363754f commit 80bcbe4

3 files changed

Lines changed: 393 additions & 5 deletions

File tree

.changeset/wild-actors-film.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+
preserve formatting for yaml format

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

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,4 +235,186 @@ world: World`;
235235
const reparsed = await loader.pull("en", result);
236236
expect(reparsed).toEqual(data);
237237
});
238+
239+
it("push should preserve mixed value quoting (some quoted, some unquoted)", async () => {
240+
const loader = createYamlLoader();
241+
loader.setDefaultLocale("en");
242+
243+
// Mixed quoting: some values quoted, some plain, some YAML references
244+
const yamlInput = `gender:
245+
f: "Feminine"
246+
m: "Masculine"
247+
female: :@f
248+
male: :@m`;
249+
250+
await loader.pull("en", yamlInput);
251+
252+
const data = {
253+
gender: {
254+
f: "Femenino",
255+
m: "Masculino",
256+
female: ":@f",
257+
male: ":@m",
258+
},
259+
};
260+
261+
const result = await loader.push("en", data, yamlInput);
262+
263+
// Quoted values should remain quoted
264+
expect(result).toContain('"Femenino"');
265+
expect(result).toContain('"Masculino"');
266+
267+
// YAML references should remain unquoted
268+
expect(result).toContain("female: :@f");
269+
expect(result).toContain("male: :@m");
270+
271+
// Should NOT quote all values globally
272+
expect(result).not.toMatch(/female:\s*":@f"/);
273+
expect(result).not.toMatch(/male:\s*":@m"/);
274+
});
275+
276+
it("push should preserve mixed key quoting (some keys quoted, some unquoted)", async () => {
277+
const loader = createYamlLoader();
278+
loader.setDefaultLocale("en");
279+
280+
// Mixed key quoting: one key quoted, others plain
281+
const yamlInput = `gender:
282+
f: Feminine
283+
"m": Masculine
284+
n: Neutral`;
285+
286+
await loader.pull("en", yamlInput);
287+
288+
const data = {
289+
gender: {
290+
f: "Femenino",
291+
m: "Masculino",
292+
n: "Neutro",
293+
},
294+
};
295+
296+
const result = await loader.push("en", data, yamlInput);
297+
298+
// Only 'm' key should be quoted
299+
expect(result).toMatch(/"m":\s*Masculino/);
300+
301+
// Other keys should NOT be quoted
302+
expect(result).toMatch(/f:\s*Femenino/);
303+
expect(result).not.toContain('"f":');
304+
expect(result).toMatch(/n:\s*Neutro/);
305+
expect(result).not.toContain('"n":');
306+
307+
// Root key should not be quoted
308+
expect(result).not.toContain('"gender":');
309+
});
310+
311+
it("push should preserve both mixed key and value quoting simultaneously", async () => {
312+
const loader = createYamlLoader();
313+
loader.setDefaultLocale("en");
314+
315+
// Complex scenario: mixed keys AND mixed values
316+
const yamlInput = `config:
317+
"special-key": "quoted value"
318+
normalKey: plain value
319+
anotherKey: "another quoted"`;
320+
321+
await loader.pull("en", yamlInput);
322+
323+
const data = {
324+
config: {
325+
"special-key": "valor citado",
326+
normalKey: "valor plano",
327+
anotherKey: "otro citado",
328+
},
329+
};
330+
331+
const result = await loader.push("en", data, yamlInput);
332+
333+
// Quoted key should remain quoted
334+
expect(result).toMatch(/"special-key":/);
335+
336+
// Other keys should not be quoted
337+
expect(result).toMatch(/normalKey:/);
338+
expect(result).not.toContain('"normalKey"');
339+
expect(result).toMatch(/anotherKey:/);
340+
expect(result).not.toContain('"anotherKey"');
341+
342+
// Quoted values should remain quoted
343+
expect(result).toContain('"valor citado"');
344+
expect(result).toContain('"otro citado"');
345+
346+
// Plain value should remain plain
347+
expect(result).toMatch(/normalKey:\s*valor plano/);
348+
});
349+
350+
it("push should preserve nested mixed quoting", async () => {
351+
const loader = createYamlLoader();
352+
loader.setDefaultLocale("en");
353+
354+
// Nested structure with mixed quoting at different levels
355+
const yamlInput = `i18n:
356+
inflections:
357+
gender:
358+
f: "Feminine"
359+
"m": "Masculine"
360+
female: :@f`;
361+
362+
await loader.pull("en", yamlInput);
363+
364+
const data = {
365+
i18n: {
366+
inflections: {
367+
gender: {
368+
f: "Femenino",
369+
m: "Masculino",
370+
female: ":@f",
371+
},
372+
},
373+
},
374+
};
375+
376+
const result = await loader.push("en", data, yamlInput);
377+
378+
// Only 'm' key should be quoted
379+
expect(result).toMatch(/"m":/);
380+
expect(result).not.toContain('"f":');
381+
382+
// Parent keys should not be quoted
383+
expect(result).not.toContain('"i18n":');
384+
expect(result).not.toContain('"inflections":');
385+
expect(result).not.toContain('"gender":');
386+
387+
// Quoted value should be quoted, reference unquoted
388+
expect(result).toContain('"Femenino"');
389+
expect(result).toMatch(/female:\s*:@f/);
390+
});
391+
392+
it("push should preserve quoting in yaml-root-key format (locale as root)", async () => {
393+
const loader = createYamlLoader();
394+
loader.setDefaultLocale("en");
395+
396+
const yamlInput = `en:
397+
"greeting": "Hello!"
398+
message: Welcome`;
399+
400+
await loader.pull("en", yamlInput);
401+
402+
const data = {
403+
en: {
404+
greeting: "¡Hola!",
405+
message: "Bienvenido",
406+
},
407+
};
408+
409+
const result = await loader.push("en", data, yamlInput);
410+
411+
// The quoted key and value should remain quoted
412+
expect(result).toContain('"greeting":');
413+
expect(result).toContain('"¡Hola!"');
414+
415+
// The unquoted key and value should remain unquoted
416+
expect(result).toMatch(/\smessage:\s/); // message key unquoted
417+
expect(result).not.toContain('"message"');
418+
expect(result).toMatch(/message:\s*Bienvenido/); // value unquoted
419+
});
238420
});

0 commit comments

Comments
 (0)