From 0f7cf0d7827e3fd6ab86765caa41fa36ecd6960b Mon Sep 17 00:00:00 2001 From: Matej Lednicky Date: Tue, 25 Feb 2025 19:01:45 +0100 Subject: [PATCH] fix(cli): bucket paths with * filenames --- .changeset/real-deers-shave.md | 5 ++ packages/cli/src/cli/utils/buckets.spec.ts | 58 +++++++++++++++++++++- packages/cli/src/cli/utils/buckets.ts | 20 +++++--- 3 files changed, 74 insertions(+), 9 deletions(-) create mode 100644 .changeset/real-deers-shave.md diff --git a/.changeset/real-deers-shave.md b/.changeset/real-deers-shave.md new file mode 100644 index 000000000..8629a378d --- /dev/null +++ b/.changeset/real-deers-shave.md @@ -0,0 +1,5 @@ +--- +"lingo.dev": patch +--- + +fix bucket path with \* filenames diff --git a/packages/cli/src/cli/utils/buckets.spec.ts b/packages/cli/src/cli/utils/buckets.spec.ts index 1aa6ac42e..5266e84d6 100644 --- a/packages/cli/src/cli/utils/buckets.spec.ts +++ b/packages/cli/src/cli/utils/buckets.spec.ts @@ -1,12 +1,16 @@ import { describe, it, expect, vi } from "vitest"; import { getBuckets } from "./buckets"; +import { glob, Path } from "glob"; vi.mock("glob", () => ({ - sync: vi.fn().mockImplementation((path) => [{ isFile: () => true, fullpath: () => path }]), + glob: { + sync: vi.fn(), + }, })); describe("getBuckets", () => { const makeI18nConfig = (include: any[]) => ({ + $schema: "https://lingo.dev/schema/i18n.json", version: 0, locale: { source: "en", @@ -20,6 +24,8 @@ describe("getBuckets", () => { }); it("should return correct buckets", () => { + mockGlobSync(["src/i18n/en.json"], ["src/translations/en/messages.json"]); + const i18nConfig = makeI18nConfig(["src/i18n/[locale].json", "src/translations/[locale]/messages.json"]); const buckets = getBuckets(i18nConfig); expect(buckets).toEqual([ @@ -33,13 +39,55 @@ describe("getBuckets", () => { ]); }); + it("should return correct buckets for paths with asterisk", () => { + mockGlobSync( + ["src/translations/landing.en.json", "src/translations/app.en.json", "src/translations/email.en.json"], + ["src/locale/landing/messages.en.json", "src/locale/app/data.en.json", "src/locale/email/custom.en.json"], + ["src/i18n/landing/en.messages.json", "src/i18n/app/en.data.json", "src/i18n/email/en.custom.json"], + [ + "src/i18n/data-landing-en-strings/en.messages.json", + "src/i18n/data-app-en-strings/en.data.json", + "src/i18n/data-email-en-strings/en.custom.json", + ], + ); + + const i18nConfig = makeI18nConfig([ + "src/translations/*.[locale].json", + "src/locale/*/*.[locale].json", + "src/i18n/*/[locale].*.json", + "src/i18n/data-*-[locale]-*/[locale].*.json", + ]); + const buckets = getBuckets(i18nConfig); + expect(buckets).toEqual([ + { + type: "json", + config: [ + { pathPattern: "src/translations/landing.[locale].json", delimiter: null }, + { pathPattern: "src/translations/app.[locale].json", delimiter: null }, + { pathPattern: "src/translations/email.[locale].json", delimiter: null }, + { pathPattern: "src/locale/landing/messages.[locale].json", delimiter: null }, + { pathPattern: "src/locale/app/data.[locale].json", delimiter: null }, + { pathPattern: "src/locale/email/custom.[locale].json", delimiter: null }, + { pathPattern: "src/i18n/landing/[locale].messages.json", delimiter: null }, + { pathPattern: "src/i18n/app/[locale].data.json", delimiter: null }, + { pathPattern: "src/i18n/email/[locale].custom.json", delimiter: null }, + { pathPattern: "src/i18n/data-landing-[locale]-strings/[locale].messages.json", delimiter: null }, + { pathPattern: "src/i18n/data-app-[locale]-strings/[locale].data.json", delimiter: null }, + { pathPattern: "src/i18n/data-email-[locale]-strings/[locale].custom.json", delimiter: null }, + ], + }, + ]); + }); + it("should return correct bucket with delimiter", () => { + mockGlobSync(["src/i18n/en.json"]); const i18nConfig = makeI18nConfig([{ path: "src/i18n/[locale].json", delimiter: "-" }]); const buckets = getBuckets(i18nConfig); expect(buckets).toEqual([{ type: "json", config: [{ pathPattern: "src/i18n/[locale].json", delimiter: "-" }] }]); }); it("should return bucket with multiple locale placeholders", () => { + mockGlobSync(["src/i18n/en/en.json"], ["src/en/translations/en/messages.json"]); const i18nConfig = makeI18nConfig([ "src/i18n/[locale]/[locale].json", "src/[locale]/translations/[locale]/messages.json", @@ -56,3 +104,11 @@ describe("getBuckets", () => { ]); }); }); + +function mockGlobSync(...args: string[][]) { + args.forEach((files) => { + vi.mocked(glob.sync).mockReturnValueOnce( + files.map((file) => ({ isFile: () => true, fullpath: () => file }) as Path), + ); + }); +} diff --git a/packages/cli/src/cli/utils/buckets.ts b/packages/cli/src/cli/utils/buckets.ts index e0d3cdbea..422ac2ea1 100644 --- a/packages/cli/src/cli/utils/buckets.ts +++ b/packages/cli/src/cli/utils/buckets.ts @@ -1,6 +1,6 @@ import _ from "lodash"; import path from "path"; -import * as glob from "glob"; +import { glob } from "glob"; import { CLIError } from "./errors"; import { I18nConfig, resolveOverridenLocale, BucketItem } from "@lingo.dev/_spec"; import { bucketTypeSchema } from "@lingo.dev/_spec"; @@ -82,13 +82,17 @@ function expandPlaceholderedGlob(_pathPattern: string, sourceLocale: string): st const sourcePathChunks = sourcePath.split(path.sep); localeSegmentIndexes.forEach((localeSegmentIndex) => { // Find the position of the "[locale]" placeholder within the segment - const localePlaceholderIndex = pathPatternChunks[localeSegmentIndex]?.indexOf("[locale]") ?? -1; - if (localeSegmentIndex >= 0 && localePlaceholderIndex >= 0) { - const placeholderedPathChunk = sourcePathChunks[localeSegmentIndex]; - const placeholderedSegment = - placeholderedPathChunk.substring(0, localePlaceholderIndex) + - "[locale]" + - placeholderedPathChunk.substring(localePlaceholderIndex + sourceLocale.length); + const pathPatternChunk = pathPatternChunks[localeSegmentIndex]; + const sourcePathChunk = sourcePathChunks[localeSegmentIndex]; + const regexp = new RegExp( + "(" + + pathPatternChunk.replaceAll(".", "\\.").replaceAll("*", ".*").replace("[locale]", `)${sourceLocale}(`) + + ")", + ); + const match = sourcePathChunk.match(regexp); + if (match) { + const [, prefix, suffix] = match; + const placeholderedSegment = prefix + "[locale]" + suffix; sourcePathChunks[localeSegmentIndex] = placeholderedSegment; } });