Skip to content

Commit

Permalink
feat(message-utils): make generateMessageId to be working in browser (#…
Browse files Browse the repository at this point in the history
…1776)

* feat(message-utils): make generateMessageId to be working in browser / lambda / etc

* refactor(message-utils): extract tests for compileMessage fn
  • Loading branch information
timofei-iatsenko authored Oct 4, 2023
1 parent efcd405 commit f879ddb
Show file tree
Hide file tree
Showing 5 changed files with 240 additions and 34 deletions.
34 changes: 7 additions & 27 deletions packages/core/src/interpolate.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { compileMessage as compile } from "@lingui/message-utils/compileMessage"
import { mockEnv, mockConsole } from "@lingui/jest-mocks"
import { interpolate } from "./interpolate"
import { Locale, Locales } from "./i18n"

Expand All @@ -9,15 +8,6 @@ describe("interpolate", () => {
return interpolate(tokens, locale || "en", locales)
}

it("should handle an error if message has syntax errors", () => {
mockConsole((console) => {
expect(compile("Invalid {message")).toEqual("Invalid {message")
expect(console.error).toBeCalledWith(
expect.stringMatching("Unexpected message end at line")
)
})
})

it("should process string chunks with provided map fn", () => {
const tokens = compile(
"Message {value, plural, one {{value} Book} other {# Books}}",
Expand All @@ -36,28 +26,18 @@ describe("interpolate", () => {
])
})

it("should compile static message", () => {
const cache = compile("Static message")
expect(cache).toEqual("Static message")

mockEnv("production", () => {
const cache = compile("Static message")
expect(cache).toEqual("Static message")
})
})

it("should compile message with variable", () => {
it("should interpolate message with variable", () => {
const cache = compile("Hey {name}!")
expect(interpolate(cache, "en", [])({ name: "Joe" })).toEqual("Hey Joe!")
})

it("should not interpolate escaped placeholder", () => {
const msg = prepare("Hey '{name}'!")

expect(msg({})).toEqual("Hey {name}!")
expect(msg({ name: "Joe" })).toEqual("Hey {name}!")
})

it("should compile plurals", () => {
it("should interpolate plurals", () => {
const plural = prepare(
"{value, plural, one {{value} Book} other {# Books}}"
)
Expand All @@ -72,7 +52,7 @@ describe("interpolate", () => {
expect(offset({ value: 3 })).toEqual("2 Books")
})

it("should compile plurals with falsy value choice", () => {
it("should interpolate plurals with falsy value choice", () => {
const plural = prepare("{value, plural, one {} other {# Books}}")
expect(plural({ value: 1 })).toEqual("")
expect(plural({ value: 2 })).toEqual("2 Books")
Expand All @@ -85,7 +65,7 @@ describe("interpolate", () => {
expect(plural({ value: 30 })).toEqual("30% discount")
})

it("should compile selectordinal", () => {
it("should interpolate selectordinal", () => {
const cache = prepare(
"{value, selectordinal, one {#st Book} two {#nd Book}}"
)
Expand Down Expand Up @@ -119,7 +99,7 @@ describe("interpolate", () => {
)
})

it("should compile select", () => {
it("should interpolate select", () => {
const cache = prepare("{value, select, female {She} other {They}}")
expect(cache({ value: "female" })).toEqual("She")
expect(cache({ value: "n/a" })).toEqual("They")
Expand Down Expand Up @@ -161,7 +141,7 @@ describe("interpolate", () => {
expectedCurrency2,
] = tc

it(`should compile custom format for locale=${locale} and locales=${locales}`, () => {
it(`should interpolate custom format for locale=${locale} and locales=${locales}`, () => {
const number = prepare("{value, number}", locale, locales)
expect(number({ value: 0.1 })).toEqual(expectedNumber)

Expand Down
3 changes: 2 additions & 1 deletion packages/message-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
"generateMessageId.js"
],
"dependencies": {
"@messageformat/parser": "^5.0.0"
"@messageformat/parser": "^5.0.0",
"js-sha256": "^0.10.1"
},
"devDependencies": {
"@lingui/jest-mocks": "workspace:^",
Expand Down
211 changes: 211 additions & 0 deletions packages/message-utils/src/compileMessage.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import { mockConsole } from "@lingui/jest-mocks"
import { compileMessage } from "./compileMessage"

describe("compileMessage", () => {
it("should handle an error if message has syntax errors", () => {
mockConsole((console) => {
expect(compileMessage("Invalid {message")).toEqual("Invalid {message")
expect(console.error).toBeCalledWith(
expect.stringMatching("Unexpected message end at line")
)
})
})

it("should process string chunks with provided map fn", () => {
const tokens = compileMessage(
"Message {value, plural, one {{value} Book} other {# Books}}",
(text) => `<${text}>`
)
expect(tokens).toEqual([
"<Message >",
[
"value",
"plural",
{
one: [["value"], "< Book>"],
other: ["#", "< Books>"],
},
],
])
})

it("should compileMessage static message", () => {
const tokens = compileMessage("Static message")
expect(tokens).toEqual("Static message")
})

it("should compileMessage message with variable", () => {
const tokens = compileMessage("Hey {name}!")
expect(tokens).toMatchInlineSnapshot(`
[
Hey ,
[
name,
],
!,
]
`)
})

it("should not interpolate escaped placeholder", () => {
const tokens = compileMessage("Hey '{name}'!")
expect(tokens).toMatchInlineSnapshot(`Hey {name}!`)
})

it("should compile plurals", () => {
const tokens = compileMessage(
"{value, plural, offset:1 =0 {No Books} one {# Book} other {# Books}}"
)
expect(tokens).toMatchInlineSnapshot(`
[
[
value,
plural,
{
0: No Books,
offset: 1,
one: [
#,
Book,
],
other: [
#,
Books,
],
},
],
]
`)
})

it("should compile selectordinal", () => {
const tokens = compileMessage(
"{value, selectordinal, one {#st Book} two {#nd Book}}"
)
expect(tokens).toMatchInlineSnapshot(`
[
[
value,
selectordinal,
{
offset: undefined,
one: [
#,
st Book,
],
two: [
#,
nd Book,
],
},
],
]
`)
})

it("should compile nested choice components", () => {
const tokens = compileMessage(
`{
gender, select,
male {{numOfGuests, plural, one {He invites one guest} other {He invites # guests}}}
female {{numOfGuests, plural, one {She invites one guest} other {She invites # guests}}}
other {They is {gender}}}`
)
expect(tokens).toMatchInlineSnapshot(`
[
[
gender,
select,
{
female: [
[
numOfGuests,
plural,
{
offset: undefined,
one: She invites one guest,
other: [
She invites ,
#,
guests,
],
},
],
],
male: [
[
numOfGuests,
plural,
{
offset: undefined,
one: He invites one guest,
other: [
He invites ,
#,
guests,
],
},
],
],
offset: undefined,
other: [
They is ,
[
gender,
],
],
},
],
]
`)
})

it("should compile select", () => {
const tokens = compileMessage("{value, select, female {She} other {They}}")
expect(tokens).toMatchInlineSnapshot(`
[
[
value,
select,
{
female: She,
offset: undefined,
other: They,
},
],
]
`)
})

it("should compile date", () => {
const tokens = compileMessage("{value, date}")
expect(tokens).toMatchInlineSnapshot(`
[
[
value,
date,
],
]
`)
})

it("should compile number", () => {
expect(compileMessage("{value, number, percent}")).toMatchInlineSnapshot(`
[
[
value,
number,
percent,
],
]
`)
expect(compileMessage("{value, number}")).toMatchInlineSnapshot(`
[
[
value,
number,
],
]
`)
})
})
18 changes: 12 additions & 6 deletions packages/message-utils/src/generateMessageId.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import crypto from "crypto"
import { sha256 } from "js-sha256"

const UNIT_SEPARATOR = "\u001F"

export function generateMessageId(msg: string, context = "") {
return crypto
.createHash("sha256")
.update(msg + UNIT_SEPARATOR + (context || ""))
.digest("base64")
.slice(0, 6)
return hexToBase64(sha256(msg + UNIT_SEPARATOR + (context || ""))).slice(0, 6)
}

function hexToBase64(hexStr: string) {
let base64 = ""
for (let i = 0; i < hexStr.length; i++) {
base64 += !((i - 1) & 1)
? String.fromCharCode(parseInt(hexStr.substring(i - 1, i + 1), 16))
: ""
}
return btoa(base64)
}
8 changes: 8 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3411,6 +3411,7 @@ __metadata:
dependencies:
"@lingui/jest-mocks": "workspace:^"
"@messageformat/parser": ^5.0.0
js-sha256: ^0.10.1
unbuild: 2.0.0
languageName: unknown
linkType: soft
Expand Down Expand Up @@ -10653,6 +10654,13 @@ __metadata:
languageName: unknown
linkType: soft

"js-sha256@npm:^0.10.1":
version: 0.10.1
resolution: "js-sha256@npm:0.10.1"
checksum: 6eb5c9f95aa902cec1930f036deb3bc664024b75fede456c0ac74b855797776c18620f47efec36787077a56ba2f3b8d6aacc7733ff8a2b5bb9ce6b655a35c5e6
languageName: node
linkType: hard

"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0":
version: 4.0.0
resolution: "js-tokens@npm:4.0.0"
Expand Down

1 comment on commit f879ddb

@vercel
Copy link

@vercel vercel bot commented on f879ddb Oct 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.