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
39 changes: 39 additions & 0 deletions packages/cli/src/api/catalog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,45 @@ describe("order", () => {
]
`)
})

it("should order by custom function", () => {
const catalog = {
"global.b": makeNextMessage({
translation: "B",
}),
"global.a": makeNextMessage({
translation: "A",
}),
"about.d": makeNextMessage({
translation: "D",
}),
"about.c": makeNextMessage({
translation: "C",
}),
}

const orderedCatalogs = order((a, b) => {
const aIsGlobal = a.messageId.startsWith("global.")
const bIsGlobal = b.messageId.startsWith("global.")

// Put `global.*` entries first
if (aIsGlobal && !bIsGlobal) return -1
if (!aIsGlobal && bIsGlobal) return 1

// Otherwise, sort alphabetically
return a.messageId.localeCompare(b.messageId)
}, catalog)

// Test that the message content is the same as before
expect(Object.keys(orderedCatalogs)).toMatchInlineSnapshot(`
[
global.a,
global.b,
about.c,
about.d,
]
`)
})
})

describe("writeCompiled", () => {
Expand Down
87 changes: 44 additions & 43 deletions packages/cli/src/api/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import path from "path"
import { globSync } from "glob"
import normalize from "normalize-path"

import { LinguiConfigNormalized, OrderBy } from "@lingui/conf"
import {
ExtractedMessageType,
LinguiConfigNormalized,
OrderBy,
OrderByFn,
} from "@lingui/conf"

import { FormatterWrapper } from "./formats"
import { CompiledCatalogNamespace } from "./compile"
Expand Down Expand Up @@ -295,52 +300,55 @@ export function order<T extends ExtractedCatalogType>(
by: OrderBy,
catalog: T
): T {
return {
messageId: orderByMessageId,
message: orderByMessage,
origin: orderByOrigin,
}[by](catalog)
const orderByFn =
typeof by === "function"
? by
: {
messageId: orderByMessageId,
message: orderByMessage,
origin: orderByOrigin,
}[by]

return Object.keys(catalog)
.sort((a, b) => {
return orderByFn(
{ messageId: a, entry: catalog[a] },
{ messageId: b, entry: catalog[b] }
)
})
.reduce((acc, key) => {
;(acc as any)[key] = catalog[key]
return acc
}, {} as T)
}
/**
* Object keys are in the same order as they were created
* https://stackoverflow.com/a/31102605/1535540
*/
function orderByMessageId<T extends ExtractedCatalogType>(messages: T): T {
return Object.keys(messages)
.sort()
.reduce((acc, key) => {
;(acc as any)[key] = messages[key]
return acc
}, {} as T)
const orderByMessageId: OrderByFn = (a, b) => {
return a.messageId.localeCompare(b.messageId)
}

function orderByOrigin<T extends ExtractedCatalogType>(messages: T): T {
function getFirstOrigin(messageKey: string) {
const sortedOrigins = messages[messageKey].origin.sort((a, b) => {
const orderByOrigin: OrderByFn = (a, b) => {
function getFirstOrigin(entry: ExtractedMessageType) {
const sortedOrigins = entry.origin.sort((a, b) => {
if (a[0] < b[0]) return -1
if (a[0] > b[0]) return 1
return 0
})
return sortedOrigins[0]
}

return Object.keys(messages)
.sort((a, b) => {
const [aFile, aLineNumber] = getFirstOrigin(a)
const [bFile, bLineNumber] = getFirstOrigin(b)
const [aFile, aLineNumber] = getFirstOrigin(a.entry)
const [bFile, bLineNumber] = getFirstOrigin(b.entry)

if (aFile < bFile) return -1
if (aFile > bFile) return 1
if (aFile < bFile) return -1
if (aFile > bFile) return 1

if (aLineNumber < bLineNumber) return -1
if (aLineNumber > bLineNumber) return 1
if (aLineNumber < bLineNumber) return -1
if (aLineNumber > bLineNumber) return 1

return 0
})
.reduce((acc, key) => {
;(acc as any)[key] = messages[key]
return acc
}, {} as T)
return 0
}

export async function writeCompiled(
Expand All @@ -367,21 +375,14 @@ export async function writeCompiled(
return filename
}

export function orderByMessage<T extends ExtractedCatalogType>(messages: T): T {
export const orderByMessage: OrderByFn = (a, b) => {
// hardcoded en-US locale to have consistent sorting
// @see https://github.com/lingui/js-lingui/pull/1808
const collator = new Intl.Collator("en-US")

return Object.keys(messages)
.sort((a, b) => {
const aMsg = messages[a].message || ""
const bMsg = messages[b].message || ""
const aCtxt = messages[a].context || ""
const bCtxt = messages[b].context || ""
return collator.compare(aMsg, bMsg) || collator.compare(aCtxt, bCtxt)
})
.reduce((acc, key) => {
;(acc as any)[key] = messages[key]
return acc
}, {} as T)
const aMsg = a.entry.message || ""
const bMsg = b.entry.message || ""
const aCtxt = a.entry.context || ""
const bCtxt = b.entry.context || ""
return collator.compare(aMsg, bMsg) || collator.compare(aCtxt, bCtxt)
}
22 changes: 21 additions & 1 deletion packages/conf/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,17 @@ export type CatalogFormatOptions = {
disableSelectWarning?: boolean
}

export type OrderBy = "messageId" | "message" | "origin"
export type OrderByFn = (
a: {
messageId: string
entry: ExtractedMessageType
},
b: {
messageId: string
entry: ExtractedMessageType
}
) => number
export type OrderBy = "messageId" | "message" | "origin" | OrderByFn

export type CatalogConfig = {
name?: string
Expand Down Expand Up @@ -242,6 +252,16 @@ export type LinguiConfig = {
catalogsMergePath?: string
/**
* Order of messages in catalog
* You can choose one of: `"messageId" | "message" | "origin"`.
*
* - `messageId` — Sorts by the message key. Not recommended if you use the source
* message as the key, as `messageId` is autogenerated and may lead to
* unexpected results.
* - `message` — Sorts by the message text and its context. **Recommended**.
* Messages are ordered alphabetically.
* - `origin` — Sorts by the location where the message was first defined.
*
* You can also provide a custom sorting function. See {@link OrderByFn} for the function signature.
*
* @default "message"
*/
Expand Down
27 changes: 27 additions & 0 deletions website/docs/ref/conf.md
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,33 @@ Sort by the message ID, `js-lingui-id` will be used if no custom id provided.

Sort by message origin (e.g. `App.js:3`)

#### Custom Function

You can provide custom sort function:

```ts
export default defineConfig({
// [...]
orderBy: (a, b) => {
/* `a` and `b` has a shape
* {
* messageId: string
* entry: ExtractedMessageType
* }
*/
const aIsGlobal = a.messageId.startsWith("global.");
const bIsGlobal = b.messageId.startsWith("global.");

// Put `global.*` entries first
if (aIsGlobal && !bIsGlobal) return -1;
if (!aIsGlobal && bIsGlobal) return 1;

// Otherwise, sort alphabetically
return a.messageId.localeCompare(b.messageId);
},
});
```

## rootDir

Default: The root of the directory containing your Lingui configuration file or the `package.json`.
Expand Down