Skip to content

Commit 401a172

Browse files
committed
feat(cli, sdk): update option text and add AbortController API
1 parent 220fb10 commit 401a172

9 files changed

Lines changed: 265 additions & 73 deletions

File tree

.changeset/tidy-numbers-speak.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"lingo.dev": minor
3+
"@lingo.dev/_sdk": minor
4+
---
5+
6+
[Enhancement]: This PR improves the test command options in the CLI by refining its functionality and usability. Additionally, it introduces the AbortController API to the SDK, enhancing request handling and cancellation support.

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ node_modules
55
.pnp
66
.pnp.js
77

8+
.idea
9+
810
# Local env files
911
.env
1012
.env.local
@@ -37,3 +39,4 @@ yarn-error.log*
3739
.DS_Store
3840
*.pem
3941
i18n.cache
42+
i18n.cache

packages/cli/src/cli/cmd/auth.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ export default new Command()
1111
.command("auth")
1212
.description("Authenticate with Lingo.dev API")
1313
.helpOption("-h, --help", "Show help")
14-
.option("--logout", "Delete existing authentication")
15-
.option("--login", "Authenticate with Lingo.dev API")
14+
.option("--logout", "Delete existing authentication and clear your saved API key.")
15+
.option("--login", "Authenticate with Lingo.dev API.")
1616
.action(async (options) => {
1717
try {
1818
let settings = await getSettings(undefined);

packages/cli/src/cli/cmd/cleanup.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ export default new Command()
1111
.command("cleanup")
1212
.description("Remove keys from target files that do not exist in the source file")
1313
.helpOption("-h, --help", "Show help")
14-
.option("--locale <locale>", "Specific locale to cleanup")
15-
.option("--bucket <bucket>", "Specific bucket to cleanup")
16-
.option("--dry-run", "Show what would be removed without making changes")
17-
.option("--verbose", "Show verbose output")
14+
.option("--locale <locale>", "Clean up only the specified target locale.If not provided, processes all target locales.")
15+
.option("--bucket <bucket>", " Clean up only the specified bucket type.If not provided, processes all buckets.")
16+
.option("--dry-run", "Show what would be removed without actually modifying any files.")
17+
.option("--verbose", "Show detailed output including:\n" +
18+
" - List of keys that would be removed.\n" +
19+
" - Processing steps.")
1820
.action(async function (options) {
1921
const ora = Ora();
2022
const results: any = [];

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@ export default new Command()
2424
.helpOption("-h, --help", "Show help")
2525
.option("--locale <locale>", "Locale to process", (val: string, prev: string[]) => (prev ? [...prev, val] : [val]))
2626
.option("--bucket <bucket>", "Bucket to process", (val: string, prev: string[]) => (prev ? [...prev, val] : [val]))
27-
.option("--key <key>", "Key to process")
28-
.option("--frozen", `Don't update the translations and fail if an update is needed`)
29-
.option("--force", "Ignore lockfile and process all keys")
30-
.option("--verbose", "Show verbose output")
31-
.option("--interactive", "Interactive mode")
32-
.option("--api-key <api-key>", "Explicitly set the API key to use")
33-
.option("--debug", "Debug mode")
34-
.option("--strict", "Stop on first error")
27+
.option("--key <key>", "Key to process.Process only a specific translation key. Useful for debugging or updating a single entry.")
28+
.option("--frozen", `Run in read-only mode - fails if any translations need updating.Useful for CI/CD pipelines to detect missing translations.`)
29+
.option("--force", "Ignore lockfile and process all keys, forcing a full re-translation.Use with caution as this may incur higher API costs.")
30+
.option("--verbose", "Show detailed output including intermediate processing data and API communication details.")
31+
.option("--interactive", "Enable interactive mode for reviewing and editing translations before they are applied.")
32+
.option("--api-key <api-key>", "Explicitly set the API key to use.Override the default API key from settings.")
33+
.option("--debug", "Pause execution at start for debugging purposes.Waits for user confirmation before proceeding.")
34+
.option("--strict", "Stop processing on first error instead of continuing with other locales/buckets.")
3535
.action(async function (options) {
3636
updateGitignore();
3737

packages/cli/src/cli/cmd/lockfile.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export default new Command()
1111
.command("lockfile")
1212
.description("Create a lockfile if it does not exist")
1313
.helpOption("-h, --help", "Show help")
14-
.option("-f, --force", "Force create a lockfile")
14+
.option("-f, --force", "Force create a lockfile.")
1515
.action(async (options) => {
1616
const flags = flagsSchema.parse(options);
1717
const ora = Ora();

packages/cli/src/cli/cmd/show/files.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import { resolveOverridenLocale } from "@lingo.dev/_spec";
99
export default new Command()
1010
.command("files")
1111
.description("Print out the list of files managed by Lingo.dev")
12-
.option("--source", "Only show source files")
13-
.option("--target", "Only show target files")
12+
.option("--source", "Only show source files.Files containing the original translations.")
13+
.option("--target", "Only show target files.Files containing translated content.")
1414
.helpOption("-h, --help", "Show help")
1515
.action(async (type) => {
1616
const ora = Ora();

packages/sdk/src/index.spec.ts

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,110 @@ describe("ReplexicaEngine", () => {
8787
});
8888
});
8989
});
90+
91+
92+
93+
describe("LingoDotDevEngine Abort Handling", () => {
94+
describe("Abort Signal Propagation", () => {
95+
it("should throw an error when abort signal is triggered before localization starts", async () => {
96+
const engine = new LingoDotDevEngine({ apiKey: "test" });
97+
const abortController = new AbortController();
98+
99+
// Abort immediately
100+
abortController.abort();
101+
102+
await expect(
103+
engine.localizeText("Test text", {
104+
sourceLocale: "en",
105+
targetLocale: "es"
106+
}, undefined, abortController.signal)
107+
).rejects.toThrow("Operation was aborted");
108+
});
109+
110+
it("should throw an error when abort signal is triggered during HTML localization", async () => {
111+
const engine = new LingoDotDevEngine({ apiKey: "test" });
112+
const abortController = new AbortController();
113+
114+
// Mock _localizeRaw to simulate a long-running operation
115+
const mockLocalizeRaw = vi.spyOn(engine as any, "_localizeRaw");
116+
mockLocalizeRaw.mockImplementation(async () => {
117+
// Simulate an asynchronous operation
118+
await new Promise(resolve => setTimeout(resolve, 100));
119+
// Abort during the operation
120+
abortController.abort();
121+
return {};
122+
});
123+
124+
await expect(
125+
engine.localizeHtml("<html><body>Test</body></html>", {
126+
sourceLocale: "en",
127+
targetLocale: "es"
128+
}, undefined, abortController.signal)
129+
).rejects.toThrow("Operation was aborted");
130+
});
131+
132+
it("should propagate abort signal through nested method calls", async () => {
133+
const engine = new LingoDotDevEngine({ apiKey: "test" });
134+
const abortController = new AbortController();
135+
136+
// Spy on internal methods to ensure abort signal is passed
137+
const spyLocalizeChunk = vi.spyOn(engine as any, "localizeChunk");
138+
const spyCheckAbortSignal = vi.spyOn(engine as any, "checkAbortSignal");
139+
140+
// Abort during object localization
141+
abortController.abort();
142+
143+
await expect(
144+
engine.localizeObject({ text: "Test" }, {
145+
sourceLocale: "en",
146+
targetLocale: "es"
147+
}, undefined, abortController.signal)
148+
).rejects.toThrow("Operation was aborted");
149+
150+
// Verify abort signal was checked in multiple places
151+
expect(spyCheckAbortSignal).toHaveBeenCalledWith(abortController.signal);
152+
});
153+
154+
it("should handle multiple concurrent abort scenarios", async () => {
155+
const engine = new LingoDotDevEngine({ apiKey: "test" });
156+
157+
const abortControllers = [
158+
new AbortController(),
159+
new AbortController(),
160+
new AbortController()
161+
];
162+
163+
const localizationPromises = abortControllers.map((controller, index) => {
164+
// Stagger abort times
165+
setTimeout(() => controller.abort(), index * 50);
166+
167+
return engine.localizeText(`Test ${index}`, {
168+
sourceLocale: "en",
169+
targetLocale: "es"
170+
}, undefined, controller.signal).catch(err => err);
171+
});
172+
173+
const results = await Promise.all(localizationPromises);
174+
175+
// All promises should result in abort errors
176+
results.forEach(result => {
177+
expect(result.message).toBe("Operation was aborted");
178+
});
179+
});
180+
181+
it("should allow localization when no abort signal is provided", async () => {
182+
const engine = new LingoDotDevEngine({ apiKey: "test" });
183+
184+
// Mock _localizeRaw to return a predictable result
185+
const mockLocalizeRaw = vi.spyOn(engine as any, "_localizeRaw");
186+
mockLocalizeRaw.mockResolvedValue({ text: "Localized Text" });
187+
188+
const result = await engine.localizeText("Test text", {
189+
sourceLocale: "en",
190+
targetLocale: "es"
191+
});
192+
193+
expect(result).toBe("Localized Text");
194+
});
195+
});
196+
});

0 commit comments

Comments
 (0)