Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(message-utils): make generateMessageId to be working in browser #1776

Merged
merged 2 commits into from Oct 4, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
34 changes: 7 additions & 27 deletions packages/core/src/interpolate.test.ts
@@ -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
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
@@ -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
@@ -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
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