diff --git a/packages/cli/src/api/__snapshots__/compile.test.ts.snap b/packages/cli/src/api/__snapshots__/compile.test.ts.snap
index c16a7b146..b60c649ca 100644
--- a/packages/cli/src/api/__snapshots__/compile.test.ts.snap
+++ b/packages/cli/src/api/__snapshots__/compile.test.ts.snap
@@ -6,6 +6,8 @@ Can't parse message. Please check correct syntax: "{value, plural, one {Book} ot
Messageformat-parser trace: Expected "#", "{", "}", doubled apostrophe, escaped string, or plain char but end of input found.
`;
+exports[`createCompiledCatalog nested message 1`] = `/*eslint-disable*/module.exports={messages:{"nested":{"one":"Uno","two":"Dos","three":"Tres","hello":["Hola ",["name"]]}}};`;
+
exports[`createCompiledCatalog options.compilerBabelOptions by default should return catalog without ASCII chars 1`] = `/*eslint-disable*/module.exports={messages:{"Hello":"Alohà"}};`;
exports[`createCompiledCatalog options.compilerBabelOptions should return catalog without ASCII chars 1`] = `/*eslint-disable*/module.exports={messages:{"Hello":"Aloh\\xE0"}};`;
@@ -20,10 +22,18 @@ exports[`createCompiledCatalog options.namespace should compile with window 1`]
exports[`createCompiledCatalog options.namespace should error with invalid value 1`] = `Invalid namespace param: "global"`;
-exports[`createCompiledCatalog options.pseudoLocale should return catalog with pseudolocalized messages 1`] = `/*eslint-disable*/module.exports={messages:{"Hello":"Ĥēĺĺō"}};`;
+exports[`createCompiledCatalog options.pseudoLocale should return catalog with pseudolocalized messages 1`] = `/*eslint-disable*/module.exports={messages:{"Hello":"ÀĥōĴ"}};`;
exports[`createCompiledCatalog options.pseudoLocale should return compiled catalog when pseudoLocale doesn't match current locale 1`] = `/*eslint-disable*/module.exports={messages:{"Hello":"Ahoj"}};`;
+exports[`createCompiledCatalog options.pure should return code catalog 1`] = `/*eslint-disable*/module.exports={messages:{"Hello":"Ahoj"}};`;
+
+exports[`createCompiledCatalog options.pure should return pure catalog 1`] = `
+Object {
+ Hello: Ahoj,
+}
+`;
+
exports[`createCompiledCatalog options.strict should return message key as a fallback translation 1`] = `/*eslint-disable*/module.exports={messages:{"Hello":"Ahoj","Missing":"Missing","Select":[["id","select",{Gen:"Genesis","1John":"1 John",other:"____"}]]}};`;
exports[`createCompiledCatalog options.strict should't return message key as a fallback in strict mode 1`] = `/*eslint-disable*/module.exports={messages:{"Hello":"Ahoj","Missing":"","Select":[["id","select",{Gen:"Genesis","1John":"1 John",other:"____"}]]}};`;
diff --git a/packages/cli/src/api/compile.test.ts b/packages/cli/src/api/compile.test.ts
index 7e41102a0..380f28dd4 100644
--- a/packages/cli/src/api/compile.test.ts
+++ b/packages/cli/src/api/compile.test.ts
@@ -2,10 +2,11 @@ import generate from "@babel/generator"
import { compile, createCompiledCatalog } from "./compile"
describe("compile", () => {
- const getSource = (message) =>
+ const getSource = (message: string) =>
generate(compile(message) as any, {
compact: true,
minified: true,
+ jsescOption: { minimal: true },
}).code
it("should optimize string only messages", () => {
@@ -67,14 +68,172 @@ describe("compile", () => {
)
})
+ it("should compile multiple plurals", () => {
+ expect(
+ getSource(
+ "{bcount, plural, one {boy} other {# boys}} {gcount, plural, one {girl} other {# girls}}"
+ )
+ ).toEqual(
+ '[["bcount","plural",{one:"boy",other:["#"," boys"]}]," ",["gcount","plural",{one:"girl",other:["#"," girls"]}]]'
+ )
+ })
+
it("should report failed message on error", () => {
expect(() =>
getSource("{value, plural, one {Book} other {Books")
).toThrowErrorMatchingSnapshot()
})
+
+ describe("with pseudo-localization", () => {
+ const getPSource = (message: string) =>
+ generate(compile(message, true) as any, {
+ compact: true,
+ minified: true,
+ jsescOption: { minimal: true },
+ }).code
+
+ it("should pseudolocalize strings", () => {
+ expect(getPSource("Martin Černý")).toEqual('"Màŕţĩń Čēŕńý"')
+ })
+
+ it("should pseudolocalize escaping syntax characters", () => {
+ // TODO: should this turn into pseudoLocale string?
+ expect(getPSource("'{name}'")).toEqual('"{name}"')
+ // expect(getPSource("'{name}'")).toEqual('"{ńàmē}"')
+ })
+
+ it("should not pseudolocalize arguments", () => {
+ expect(getPSource("{name}")).toEqual('[["name"]]')
+ expect(getPSource("B4 {name} A4")).toEqual('["ß4 ",["name"]," À4"]')
+ })
+
+ it("should not pseudolocalize arguments nor formats", () => {
+ expect(getPSource("{name, number}")).toEqual('[["name","number"]]')
+ expect(getPSource("{name, number, percent}")).toEqual(
+ '[["name","number","percent"]]'
+ )
+ })
+
+ it("should not pseudolocalize HTML tags", () => {
+ expect(getPSource('Martin Černý')).toEqual(
+ JSON.stringify('Màŕţĩń Čēŕńý')
+ )
+ expect(
+ getPSource("Martin Cerny 123aČerný")
+ ).toEqual(
+ JSON.stringify("Màŕţĩń Ćēŕńŷ 123àČēŕńý")
+ )
+ expect(getPSource("Martin a")).toEqual(
+ JSON.stringify("Màŕţĩń à")
+ )
+ expect(getPSource("text")).toEqual(
+ JSON.stringify("ţēxţ")
+ )
+ })
+
+ describe("Plurals", () => {
+ it("with value", () => {
+ expect(
+ getPSource("{value, plural, one {# book} other {# books}}")
+ ).toEqual('[["value","plural",{one:["#"," ƀōōķ"],other:["#"," ƀōōķś"]}]]')
+ })
+
+ it("with variable placeholder", () => {
+ expect(
+ getPSource(
+ "{count, plural, one {{countString} book} other {{countString} books}}"
+ )
+ ).toEqual(
+ '[["count","plural",{one:[["countString"]," ƀōōķ"],other:[["countString"]," ƀōōķś"]}]]'
+ )
+ })
+
+ it("with offset", () => {
+ expect(
+ getPSource(
+ "{count, plural, offset:1 zero {There are no messages} other {There are # messages in your inbox}}"
+ )
+ ).toEqual(
+ '[["count","plural",{offset:1,zero:"Ţĥēŕē àŕē ńō mēśśàĝēś",other:["Ţĥēŕē àŕē ","#"," mēśśàĝēś ĩń ŷōũŕ ĩńƀōx"]}]]'
+ )
+ })
+
+ it("with HTML tags", () => {
+ expect(
+ getPSource(
+ "{count, plural, zero {There's # message} other {There are # messages}}"
+ )
+ ).toEqual(
+ '[["count","plural",{zero:["Ţĥēŕē\'ś ","#"," mēśśàĝē"],other:["Ţĥēŕē àŕē ","#"," mēśśàĝēś"]}]]'
+ )
+ })
+
+ it("with exact number", () => {
+ expect(
+ getPSource(
+ "{count, plural, =0 {There's # message} other {There are # messages}}"
+ )
+ ).toEqual(
+ '[["count","plural",{0:["Ţĥēŕē\'ś ","#"," mēśśàĝē"],other:["Ţĥēŕē àŕē ","#"," mēśśàĝēś"]}]]'
+ )
+ })
+ })
+
+ it("SelectOrdinal", () => {
+ expect(
+ getPSource(
+ "{count, selectordinal, offset:1 one {#st} two {#nd} few {#rd} =4 {4th} many {testMany} other {#th}}"
+ )
+ ).toEqual(
+ '[["count","selectordinal",{offset:1,one:["#","śţ"],two:["#","ńď"],few:["#","ŕď"],4:"4ţĥ",many:"ţēśţMàńŷ",other:["#","ţĥ"]}]]'
+ )
+ })
+
+ it("Select", () => {
+ expect(
+ getPSource(
+ "{gender, select, male {He} female {She} other {Other}}"
+ )
+ ).toEqual(
+ '[["gender","select",{male:"Ĥē",female:"Śĥē",other:"Ōţĥēŕ"}]]'
+ )
+ })
+
+ it("should not pseudolocalize variables", () => {
+ expect(getPSource("replace {count}")).toEqual('["ŕēƥĺàćē ",["count"]]')
+ expect(getPSource("replace { count }")).toEqual('["ŕēƥĺàćē ",["count"]]')
+ })
+
+ it("Multiple Plurals", () => {
+ expect(
+ getPSource(
+ "{bcount, plural, one {boy} other {# boys}} {gcount, plural, one {girl} other {# girls}}"
+ )
+ ).toEqual(
+ '[["bcount","plural",{one:"ƀōŷ",other:["#"," ƀōŷś"]}]," ",["gcount","plural",{one:"ĝĩŕĺ",other:["#"," ĝĩŕĺś"]}]]'
+ )
+ })
+ })
})
describe("createCompiledCatalog", () => {
+ it("nested message", () => {
+ expect(
+ createCompiledCatalog(
+ "cs",
+ {
+ nested: {
+ one: "Uno",
+ two: "Dos",
+ three: "Tres",
+ hello: "Hola {name}",
+ },
+ },
+ {}
+ )
+ ).toMatchSnapshot()
+ })
+
describe("options.namespace", () => {
const getCompiledCatalog = (namespace) =>
createCompiledCatalog(
@@ -113,7 +272,7 @@ describe("createCompiledCatalog", () => {
{
Hello: "Ahoj",
Missing: "",
- Select: "{id, select, Gen {Genesis} 1John {1 John} other {____}}"
+ Select: "{id, select, Gen {Genesis} 1John {1 John} other {____}}",
},
{
strict,
@@ -150,6 +309,27 @@ describe("createCompiledCatalog", () => {
})
})
+ describe("options.pure", () => {
+ const getCompiledCatalog = (pure) =>
+ createCompiledCatalog(
+ "ps",
+ {
+ Hello: "Ahoj",
+ },
+ {
+ pure,
+ }
+ )
+
+ it("should return pure catalog", () => {
+ expect(getCompiledCatalog(true)).toMatchSnapshot()
+ })
+
+ it("should return code catalog", () => {
+ expect(getCompiledCatalog(false)).toMatchSnapshot()
+ })
+ })
+
describe("options.compilerBabelOptions", () => {
const getCompiledCatalog = (opts = {}) =>
createCompiledCatalog(
@@ -165,13 +345,15 @@ describe("createCompiledCatalog", () => {
})
it("should return catalog without ASCII chars", () => {
- expect(getCompiledCatalog({
- compilerBabelOptions: {
- jsescOption: {
- minimal: false,
- }
- }
- })).toMatchSnapshot()
+ expect(
+ getCompiledCatalog({
+ compilerBabelOptions: {
+ jsescOption: {
+ minimal: false,
+ },
+ },
+ })
+ ).toMatchSnapshot()
})
})
})
diff --git a/packages/cli/src/api/compile.ts b/packages/cli/src/api/compile.ts
index 0ebd8add7..42e0882c0 100644
--- a/packages/cli/src/api/compile.ts
+++ b/packages/cli/src/api/compile.ts
@@ -5,7 +5,6 @@ import * as R from "ramda"
import pseudoLocalize from "./pseudoLocalize"
-
const INVALID_OBJECT_KEY_REGEX = /^(\d+[a-zA-Z]|[a-zA-Z]+\d)(\d|[a-zA-Z])*/
export type CompiledCatalogNamespace = "cjs" | "es" | "ts" | string
@@ -26,11 +25,7 @@ export type CreateCompileCatalogOptions = {
* applying pseudolocalization where necessary.
*/
function compileSingleKey(key: string, translation: string, shouldPseudolocalize: boolean): t.ObjectProperty {
- if (shouldPseudolocalize) {
- translation = pseudoLocalize(key)
- }
-
- return t.objectProperty(t.stringLiteral(key), compile(translation))
+ return t.objectProperty(t.stringLiteral(key), compile(translation, shouldPseudolocalize))
}
export function createCompiledCatalog(
@@ -118,34 +113,40 @@ function buildExportStatement(expression, namespace: CompiledCatalogNamespace) {
* Compile string message into AST tree. Message format is parsed/compiled into
* JS arrays, which are handled in client.
*/
-export function compile(message: string) {
+export function compile(message: string, shouldPseudolocalize: boolean = false) {
let tokens
try {
tokens = parse(message)
} catch (e) {
throw new Error(
- `Can't parse message. Please check correct syntax: "${message}" \n \n Messageformat-parser trace: ${e.message}`,
+ `Can't parse message. Please check correct syntax: "${message}" \n \n Messageformat-parser trace: ${e.message}`
)
}
- const ast = processTokens(tokens)
+ const ast = processTokens(tokens, shouldPseudolocalize)
if (isString(ast)) return t.stringLiteral(ast)
return ast
}
-function processTokens(tokens) {
+function processTokens(tokens, shouldPseudolocalize: boolean) {
// Shortcut - if the message doesn't include any formatting,
// simply join all string chunks into one message
if (!tokens.filter((token) => !isString(token)).length) {
- return tokens.join("")
+ if (shouldPseudolocalize) {
+ return tokens.map((token) => pseudoLocalize(token)).join("")
+ } else {
+ return tokens.join("")
+ }
}
return t.arrayExpression(
tokens.map((token) => {
if (isString(token)) {
- return t.stringLiteral(token)
+ return t.stringLiteral(
+ shouldPseudolocalize ? pseudoLocalize(token) : token
+ )
// # in plural case
} else if (token.type === "octothorpe") {
@@ -179,7 +180,7 @@ function processTokens(tokens) {
}
token.cases.forEach((item) => {
- const inlineTokens = processTokens(item.tokens)
+ const inlineTokens = processTokens(item.tokens, shouldPseudolocalize)
formatProps.push(
t.objectProperty(
// if starts with number must be wrapped with quotes
diff --git a/packages/cli/src/api/pseudoLocalize.test.ts b/packages/cli/src/api/pseudoLocalize.test.ts
index 4dd0b187b..1b9c7d207 100644
--- a/packages/cli/src/api/pseudoLocalize.test.ts
+++ b/packages/cli/src/api/pseudoLocalize.test.ts
@@ -50,20 +50,20 @@ describe("PseudoLocalization", () => {
it("with HTML tags", () => {
expect(
pseudoLocalize(
- "{count, plural, zero {There's # message} other {There are # messages}"
+ "{count, plural, zero {There's # message} other {There are # messages}}"
)
).toEqual(
- "{count, plural, zero {Ţĥēŕē'ś # mēśśàĝē} other {Ţĥēŕē àŕē # mēśśàĝēś}"
+ "{count, plural, zero {Ţĥēŕē'ś # mēśśàĝē} other {Ţĥēŕē àŕē # mēśśàĝēś}}"
)
})
it("with exact number", () => {
expect(
pseudoLocalize(
- "{count, plural, =0 {There's # message} other {There are # messages}"
+ "{count, plural, =0 {There's # message} other {There are # messages}}"
)
).toEqual(
- "{count, plural, =0 {Ţĥēŕē'ś # mēśśàĝē} other {Ţĥēŕē àŕē # mēśśàĝēś}"
+ "{count, plural, =0 {Ţĥēŕē'ś # mēśśàĝē} other {Ţĥēŕē àŕē # mēśśàĝēś}}"
)
})
})
@@ -92,4 +92,14 @@ describe("PseudoLocalization", () => {
expect(pseudoLocalize("replace {count}")).toEqual("ŕēƥĺàćē {count}")
expect(pseudoLocalize("replace { count }")).toEqual("ŕēƥĺàćē { count }")
})
+
+ it("multiple plurals pseudolocalize gives wrong ICU message", () => {
+ expect(
+ pseudoLocalize(
+ "{bcount, plural, one {boy} other {# boys}} {gcount, plural, one {girl} other {# girls}}"
+ )
+ ).not.toEqual(
+ "{bcount, plural, one {ƀōŷ} other {# ƀōŷś}} {gcount, plural, one {ĝĩŕĺ} other {# ĝĩŕĺś}}"
+ )
+ })
})