Skip to content

Commit d2963fc

Browse files
authored
fix: partial cache restore (#483)
1 parent 85c7998 commit d2963fc

File tree

6 files changed

+109
-20
lines changed

6 files changed

+109
-20
lines changed

.changeset/swift-panthers-unite.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 partial cache restore

packages/cli/src/cli/cmd/i18n.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ export default new Command()
105105
bucketOra.info(`Processing path: ${bucketConfig.pathPattern}`);
106106

107107
const sourceLocale = resolveOverridenLocale(i18nConfig!.locale.source, bucketConfig.delimiter);
108-
const bucketLoader = createBucketLoader(bucket.type, bucketConfig.pathPattern);
108+
const bucketLoader = createBucketLoader(bucket.type, bucketConfig.pathPattern, { isCacheRestore: true });
109109
bucketLoader.setDefaultLocale(sourceLocale);
110110
await bucketLoader.init();
111111
const sourceData = await bucketLoader.pull(sourceLocale);

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,42 @@ describe("bucket loaders", () => {
368368

369369
expect(fs.writeFile).toHaveBeenCalledWith("i18n/es.json", expectedOutput, { encoding: "utf-8", flag: "w" });
370370
});
371+
372+
it("should use key values from original input for missing keys", async () => {
373+
setupFileMocks();
374+
375+
const input = { "button.title": "Submit", "button.description": "Submit description" };
376+
const payload = { "button.title": "Enviar" };
377+
const expectedOutput = JSON.stringify({ ...input, ...payload }, null, 2);
378+
379+
mockFileOperations(JSON.stringify(input));
380+
381+
const jsonLoader = createBucketLoader("json", "i18n/[locale].json");
382+
jsonLoader.setDefaultLocale("en");
383+
await jsonLoader.pull("en");
384+
385+
await jsonLoader.push("es", payload);
386+
387+
expect(fs.writeFile).toHaveBeenCalledWith("i18n/es.json", expectedOutput, { encoding: "utf-8", flag: "w" });
388+
});
389+
390+
it("should not use key values from original input on cache restoration", async () => {
391+
setupFileMocks();
392+
393+
const input = { "button.title": "Submit", "button.description": "Submit description" };
394+
const payload = { "button.title": "Enviar" };
395+
const expectedOutput = JSON.stringify(payload, null, 2);
396+
397+
mockFileOperations(JSON.stringify(input));
398+
399+
const jsonLoader = createBucketLoader("json", "i18n/[locale].json", { isCacheRestore: true });
400+
jsonLoader.setDefaultLocale("en");
401+
await jsonLoader.pull("en");
402+
403+
await jsonLoader.push("es", payload);
404+
405+
expect(fs.writeFile).toHaveBeenCalledWith("i18n/es.json", expectedOutput, { encoding: "utf-8", flag: "w" });
406+
});
371407
});
372408

373409
describe("markdown bucket loader", () => {

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

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,14 @@ import createVariableLoader from "./variable";
2828
import createSyncLoader from "./sync";
2929
import createPlutilJsonTextLoader from "./plutil-json-loader";
3030

31+
type BucketLoaderOptions = {
32+
isCacheRestore?: boolean;
33+
};
34+
3135
export default function createBucketLoader(
3236
bucketType: Z.infer<typeof bucketTypeSchema>,
3337
bucketPathPattern: string,
38+
{ isCacheRestore = false }: BucketLoaderOptions = {},
3439
): ILoader<void, Record<string, string>> {
3540
switch (bucketType) {
3641
default:
@@ -41,23 +46,23 @@ export default function createBucketLoader(
4146
createAndroidLoader(),
4247
createFlatLoader(),
4348
createSyncLoader(),
44-
createUnlocalizableLoader(),
49+
createUnlocalizableLoader(isCacheRestore),
4550
);
4651
case "csv":
4752
return composeLoaders(
4853
createTextFileLoader(bucketPathPattern),
4954
createCsvLoader(),
5055
createFlatLoader(),
5156
createSyncLoader(),
52-
createUnlocalizableLoader(),
57+
createUnlocalizableLoader(isCacheRestore),
5358
);
5459
case "html":
5560
return composeLoaders(
5661
createTextFileLoader(bucketPathPattern),
5762
createPrettierLoader({ parser: "html", alwaysFormat: true }),
5863
createHtmlLoader(),
5964
createSyncLoader(),
60-
createUnlocalizableLoader(),
65+
createUnlocalizableLoader(isCacheRestore),
6166
);
6267
case "json":
6368
return composeLoaders(
@@ -66,46 +71,46 @@ export default function createBucketLoader(
6671
createJsonLoader(),
6772
createFlatLoader(),
6873
createSyncLoader(),
69-
createUnlocalizableLoader(),
74+
createUnlocalizableLoader(isCacheRestore),
7075
);
7176
case "markdown":
7277
return composeLoaders(
7378
createTextFileLoader(bucketPathPattern),
7479
createPrettierLoader({ parser: "markdown" }),
7580
createMarkdownLoader(),
7681
createSyncLoader(),
77-
createUnlocalizableLoader(),
82+
createUnlocalizableLoader(isCacheRestore),
7883
);
7984
case "po":
8085
return composeLoaders(
8186
createTextFileLoader(bucketPathPattern),
8287
createPoLoader(),
8388
createFlatLoader(),
8489
createSyncLoader(),
85-
createUnlocalizableLoader(),
90+
createUnlocalizableLoader(isCacheRestore),
8691
createVariableLoader({ type: "python" }),
8792
);
8893
case "properties":
8994
return composeLoaders(
9095
createTextFileLoader(bucketPathPattern),
9196
createPropertiesLoader(),
9297
createSyncLoader(),
93-
createUnlocalizableLoader(),
98+
createUnlocalizableLoader(isCacheRestore),
9499
);
95100
case "xcode-strings":
96101
return composeLoaders(
97102
createTextFileLoader(bucketPathPattern),
98103
createXcodeStringsLoader(),
99104
createSyncLoader(),
100-
createUnlocalizableLoader(),
105+
createUnlocalizableLoader(isCacheRestore),
101106
);
102107
case "xcode-stringsdict":
103108
return composeLoaders(
104109
createTextFileLoader(bucketPathPattern),
105110
createXcodeStringsdictLoader(),
106111
createFlatLoader(),
107112
createSyncLoader(),
108-
createUnlocalizableLoader(),
113+
createUnlocalizableLoader(isCacheRestore),
109114
);
110115
case "xcode-xcstrings":
111116
return composeLoaders(
@@ -115,7 +120,7 @@ export default function createBucketLoader(
115120
createXcodeXcstringsLoader(),
116121
createFlatLoader(),
117122
createSyncLoader(),
118-
createUnlocalizableLoader(),
123+
createUnlocalizableLoader(isCacheRestore),
119124
createVariableLoader({ type: "ieee" }),
120125
);
121126
case "yaml":
@@ -125,7 +130,7 @@ export default function createBucketLoader(
125130
createYamlLoader(),
126131
createFlatLoader(),
127132
createSyncLoader(),
128-
createUnlocalizableLoader(),
133+
createUnlocalizableLoader(isCacheRestore),
129134
);
130135
case "yaml-root-key":
131136
return composeLoaders(
@@ -135,7 +140,7 @@ export default function createBucketLoader(
135140
createRootKeyLoader(true),
136141
createFlatLoader(),
137142
createSyncLoader(),
138-
createUnlocalizableLoader(),
143+
createUnlocalizableLoader(isCacheRestore),
139144
);
140145
case "flutter":
141146
return composeLoaders(
@@ -145,44 +150,44 @@ export default function createBucketLoader(
145150
createFlutterLoader(),
146151
createFlatLoader(),
147152
createSyncLoader(),
148-
createUnlocalizableLoader(),
153+
createUnlocalizableLoader(isCacheRestore),
149154
);
150155
case "xliff":
151156
return composeLoaders(
152157
createTextFileLoader(bucketPathPattern),
153158
createXliffLoader(),
154159
createFlatLoader(),
155160
createSyncLoader(),
156-
createUnlocalizableLoader(),
161+
createUnlocalizableLoader(isCacheRestore),
157162
);
158163
case "xml":
159164
return composeLoaders(
160165
createTextFileLoader(bucketPathPattern),
161166
createXmlLoader(),
162167
createFlatLoader(),
163168
createSyncLoader(),
164-
createUnlocalizableLoader(),
169+
createUnlocalizableLoader(isCacheRestore),
165170
);
166171
case "srt":
167172
return composeLoaders(
168173
createTextFileLoader(bucketPathPattern),
169174
createSrtLoader(),
170175
createSyncLoader(),
171-
createUnlocalizableLoader(),
176+
createUnlocalizableLoader(isCacheRestore),
172177
);
173178
case "dato":
174179
return composeLoaders(
175180
createDatoLoader(bucketPathPattern),
176181
createSyncLoader(),
177182
createFlatLoader(),
178-
createUnlocalizableLoader(),
183+
createUnlocalizableLoader(isCacheRestore),
179184
);
180185
case "vtt":
181186
return composeLoaders(
182187
createTextFileLoader(bucketPathPattern),
183188
createVttLoader(),
184189
createSyncLoader(),
185-
createUnlocalizableLoader(),
190+
createUnlocalizableLoader(isCacheRestore),
186191
);
187192
}
188193
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { describe, expect, it } from "vitest";
2+
import createUnlocalizableLoader from "./unlocalizable";
3+
4+
describe("unlocalizable loader", () => {
5+
const data = {
6+
foo: "bar",
7+
num: 1,
8+
empty: "",
9+
bool: true,
10+
isoDate: "2025-02-21",
11+
bar: "foo",
12+
url: "https://example.com",
13+
systemId: "Ab1cdefghijklmnopqrst2",
14+
};
15+
16+
describe.each([true, false])("cache restoration '%s'", (cacheRestoration) => {
17+
it("should remove unlocalizable keys on pull", async () => {
18+
const loader = createUnlocalizableLoader(cacheRestoration);
19+
loader.setDefaultLocale("en");
20+
const result = await loader.pull("en", data);
21+
22+
expect(result).toEqual({ foo: "bar", bar: "foo" });
23+
});
24+
25+
it("should handle unlocalizable keys on push", async () => {
26+
const pushData = { foo: "bar-es", bar: "foo-es" };
27+
28+
const loader = createUnlocalizableLoader(cacheRestoration);
29+
loader.setDefaultLocale("en");
30+
await loader.pull("en", data);
31+
const result = await loader.push("es", pushData);
32+
33+
const expectedData = cacheRestoration ? { ...pushData } : { ...data, ...pushData };
34+
expect(result).toEqual(expectedData);
35+
});
36+
});
37+
});

packages/cli/src/cli/loaders/unlocalizable.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import { isValid, parseISO } from "date-fns";
55
import { ILoader } from "./_types";
66
import { createLoader } from "./_utils";
77

8-
export default function createUnlocalizableLoader(): ILoader<Record<string, any>, Record<string, any>> {
8+
export default function createUnlocalizableLoader(
9+
isCacheRestore: boolean = false,
10+
): ILoader<Record<string, any>, Record<string, any>> {
911
const rules = {
1012
isEmpty: (v: any) => _.isEmpty(v),
1113
isNumber: (v: string) => !_.isNaN(_.toNumber(v)),
@@ -32,6 +34,10 @@ export default function createUnlocalizableLoader(): ILoader<Record<string, any>
3234
return result;
3335
},
3436
async push(locale, data, originalInput) {
37+
if (isCacheRestore) {
38+
return _.merge({}, data);
39+
}
40+
3541
const result = _.merge({}, originalInput, data);
3642
return result;
3743
},

0 commit comments

Comments
 (0)