Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 179 additions & 0 deletions packages/cli/src/api/__snapshots__/catalog.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,44 @@ exports[`Catalog POT Flow Should get translations from template if locale file n
}
`;

exports[`Catalog collect should extract files with special characters when passed in options 1`] = `
{
Component C: {
comments: [],
context: undefined,
message: undefined,
origin: [
[
collect/(componentC)/index.js,
1,
],
],
},
Component D: {
comments: [],
context: undefined,
message: undefined,
origin: [
[
collect/[componentD]/index.js,
1,
],
],
},
Component E: {
comments: [],
context: undefined,
message: undefined,
origin: [
[
collect/$componentE/index.js,
1,
],
],
},
}
`;

exports[`Catalog collect should extract messages from source files 1`] = `
{
Component A: {
Expand All @@ -31,6 +69,39 @@ exports[`Catalog collect should extract messages from source files 1`] = `
],
],
},
Component C: {
comments: [],
context: undefined,
message: undefined,
origin: [
[
collect/(componentC)/index.js,
1,
],
],
},
Component D: {
comments: [],
context: undefined,
message: undefined,
origin: [
[
collect/[componentD]/index.js,
1,
],
],
},
Component E: {
comments: [],
context: undefined,
message: undefined,
origin: [
[
collect/$componentE/index.js,
1,
],
],
},
Hello World: {
comments: [
Comment A,
Expand Down Expand Up @@ -422,6 +493,60 @@ exports[`Catalog make should merge with existing catalogs 2`] = `
],
translation: ,
},
Component C: {
comments: [
js-lingui-explicit-id,
],
context: null,
extra: {
flags: [],
translatorComments: [],
},
obsolete: false,
origin: [
[
collect/(componentC)/index.js,
1,
],
],
translation: ,
},
Component D: {
comments: [
js-lingui-explicit-id,
],
context: null,
extra: {
flags: [],
translatorComments: [],
},
obsolete: false,
origin: [
[
collect/[componentD]/index.js,
1,
],
],
translation: ,
},
Component E: {
comments: [
js-lingui-explicit-id,
],
context: null,
extra: {
flags: [],
translatorComments: [],
},
obsolete: false,
origin: [
[
collect/$componentE/index.js,
1,
],
],
translation: ,
},
Hello World: {
comments: [
Comment A,
Expand Down Expand Up @@ -519,6 +644,60 @@ exports[`Catalog make should merge with existing catalogs 2`] = `
],
translation: ,
},
Component C: {
comments: [
js-lingui-explicit-id,
],
context: null,
extra: {
flags: [],
translatorComments: [],
},
obsolete: false,
origin: [
[
collect/(componentC)/index.js,
1,
],
],
translation: ,
},
Component D: {
comments: [
js-lingui-explicit-id,
],
context: null,
extra: {
flags: [],
translatorComments: [],
},
obsolete: false,
origin: [
[
collect/[componentD]/index.js,
1,
],
],
translation: ,
},
Component E: {
comments: [
js-lingui-explicit-id,
],
context: null,
extra: {
flags: [],
translatorComments: [],
},
obsolete: false,
origin: [
[
collect/$componentE/index.js,
1,
],
],
translation: ,
},
Hello World: {
comments: [
Comment A,
Expand Down
22 changes: 22 additions & 0 deletions packages/cli/src/api/catalog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,28 @@ describe("Catalog", () => {
expect(messages).toMatchSnapshot()
})

it("should extract files with special characters when passed in options", async () => {
const catalog = new Catalog(
{
name: "messages",
path: "locales/{locale}",
include: [fixture("collect")],
exclude: [],
format,
},
mockConfig()
)

const messages = await catalog.collect({
files: [
fixture("collect/(componentC)/index.js"),
fixture("collect/[componentD]/index.js"),
fixture("collect/$componentE/index.js"),
],
})
expect(messages).toMatchSnapshot()
})

it("should throw an error when duplicate identifier with different defaults found", async () => {
const catalog = new Catalog(
{
Expand Down
5 changes: 4 additions & 1 deletion packages/cli/src/api/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { mergeCatalog } from "./catalog/mergeCatalog"
import { extractFromFiles } from "./catalog/extractFromFiles"
import {
isDirectory,
makePathRegexSafe,
normalizeRelativePath,
replacePlaceholders,
writeFile,
Expand Down Expand Up @@ -132,7 +133,9 @@ export class Catalog {
): Promise<ExtractedCatalogType | undefined> {
let paths = this.sourcePaths
if (options.files) {
options.files = options.files.map((p) => normalize(p, false))
options.files = options.files.map((p) =>
makePathRegexSafe(normalize(p, false))
)
const regex = new RegExp(options.files.join("|"), "i")
paths = paths.filter((path: string) => regex.test(path))
}
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/api/fixtures/collect/$componentE/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/*i18n*/ i18n._("Component E")
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/*i18n*/ i18n._("Component C")
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/*i18n*/ i18n._("Component D")
74 changes: 73 additions & 1 deletion packages/cli/src/api/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { normalizeRelativePath, replacePlaceholders } from "./utils"
import {
makePathRegexSafe,
normalizeRelativePath,
replacePlaceholders,
} from "./utils"
import mockFs from "mock-fs"

describe("replacePlaceholders", () => {
Expand Down Expand Up @@ -63,3 +67,71 @@ describe("normalizeRelativePath", () => {
expect(normalizeRelativePath("./componentB")).toEqual("componentB")
})
})

describe("makePathRegexSafe", () => {
it("should not modify paths without special characters", () => {
const path = "src/components/test.tsx"
expect(makePathRegexSafe(path)).toBe(path)
})

it("should escape parentheses", () => {
const path = "src/(components)/test.tsx"
expect(makePathRegexSafe(path)).toBe("src/\\(components\\)/test.tsx")
})

it("should escape square brackets", () => {
const path = "src/[components]/test.tsx"
expect(makePathRegexSafe(path)).toBe("src/\\[components\\]/test.tsx")
})

it("should escape curly braces", () => {
const path = "src/{components}/test.tsx"
expect(makePathRegexSafe(path)).toBe("src/\\{components\\}/test.tsx")
})

it("should escape caret symbol", () => {
const path = "src/^components/test.tsx"
expect(makePathRegexSafe(path)).toBe("src/\\^components/test.tsx")
})

it("should escape dollar sign", () => {
const path = "src/$components/test.tsx"
expect(makePathRegexSafe(path)).toBe("src/\\$components/test.tsx")
})

it("should escape plus sign", () => {
const path = "src/+components/test.tsx"
expect(makePathRegexSafe(path)).toBe("src/\\+components/test.tsx")
})

it("should handle multiple special characters", () => {
const path = "src/components/test(1)[2]{3}^$+.tsx"
expect(makePathRegexSafe(path)).toBe(
"src/components/test\\(1\\)\\[2\\]\\{3\\}\\^\\$\\+.tsx"
)
})

it("should handle paths with spaces", () => {
const path = "src/components/test component.tsx"
expect(makePathRegexSafe(path)).toBe("src/components/test component.tsx")
})

it("should handle empty string", () => {
expect(makePathRegexSafe("")).toBe("")
})

it("should handle root-level path", () => {
const path = "test.tsx"
expect(makePathRegexSafe(path)).toBe("test.tsx")
})

it("should handle relative paths", () => {
const path = "./src/components/test[1].tsx"
expect(makePathRegexSafe(path)).toBe("./src/components/test\\[1\\].tsx")
})

it("should handle paths with consecutive special characters", () => {
const path = "src/components/test[[]].tsx"
expect(makePathRegexSafe(path)).toBe("src/components/test\\[\\[\\]\\].tsx")
})
})
7 changes: 7 additions & 0 deletions packages/cli/src/api/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,10 @@ export function normalizeRelativePath(sourcePath: string): string {
(isDir ? "/" : "")
)
}

/**
* Escape special regex characters used in file-based routing systems
*/
export function makePathRegexSafe(path: string) {
return path.replace(/[(){}[\]^$+]/g, "\\$&")
}