Skip to content

Commit 18c4786

Browse files
authored
feat: support pre-compile locale messages (#32)
* feat: support pre-compile locale messages * updates * upgrade
1 parent d47f2d3 commit 18c4786

File tree

10 files changed

+687
-560
lines changed

10 files changed

+687
-560
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
"ts-jest": "^25.3.0",
6363
"typescript": "^3.8.3",
6464
"typescript-eslint-language-service": "^2.0.3",
65-
"vue": "^3.0.0-beta.4"
65+
"vue": "^3.0.0-beta.5"
6666
},
6767
"engines": {
6868
"node": ">= 10"
@@ -85,7 +85,7 @@
8585
"main": "dist/vue-i18n.cjs.js",
8686
"module": "dist/vue-i18n.esm-bundler.js",
8787
"peerDependencies": {
88-
"vue": "^3.0.0-beta.4"
88+
"vue": "^3.0.0-beta.5"
8989
},
9090
"repository": {
9191
"type": "git",

src/composer.ts

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
} from 'vue'
1717
import { WritableComputedRef, ComputedRef } from '@vue/reactivity'
1818
import { apply } from './plugin'
19-
import { Path } from './path'
19+
import { Path, parse as parsePath } from './path'
2020
import {
2121
DateTimeFormats,
2222
NumberFormats,
@@ -27,6 +27,7 @@ import {
2727
LinkedModifiers,
2828
PluralizationRules,
2929
NamedValue,
30+
MessageFunctions,
3031
MessageProcessor
3132
} from './message/runtime'
3233
import {
@@ -77,7 +78,11 @@ export type MissingHandler = (
7778
type?: string
7879
) => string | void
7980

80-
export type CustomBlocks = string[]
81+
export type PreCompileHandler = () => {
82+
messages: LocaleMessages
83+
functions: MessageFunctions
84+
}
85+
export type CustomBlocks = string[] | PreCompileHandler
8186

8287
/**
8388
* Composer Options
@@ -189,21 +194,58 @@ function getLocaleMessages(
189194
locale: Locale
190195
): LocaleMessages {
191196
const { messages, __i18n } = options
197+
192198
// prettier-ignore
193199
let ret = isPlainObject(messages)
194200
? messages
195201
: isArray(__i18n)
196202
? {}
197203
: { [locale]: {} }
204+
198205
// merge locale messages of i18n custom block
199206
if (isArray(__i18n)) {
200207
__i18n.forEach(raw => {
201208
ret = Object.assign(ret, JSON.parse(raw))
202209
})
210+
return ret
203211
}
212+
213+
if (isFunction(__i18n)) {
214+
const { functions } = __i18n()
215+
addPreCompileMessages(ret, functions)
216+
}
217+
204218
return ret
205219
}
206220

221+
function addPreCompileMessages(
222+
messages: LocaleMessages,
223+
functions: MessageFunctions
224+
): void {
225+
const keys = Object.keys(functions)
226+
keys.forEach(key => {
227+
const compiled = functions[key]
228+
const { l, k } = JSON.parse(key)
229+
const targetLocaleMessage = (messages[l] = messages[l] || {})
230+
const paths = parsePath(k)
231+
if (paths != null) {
232+
const len = paths.length
233+
let last = targetLocaleMessage as any // eslint-disable-line @typescript-eslint/no-explicit-any
234+
let i = 0
235+
while (i < len) {
236+
const path = paths[i]
237+
const val = last[path]
238+
if (val != null) {
239+
last[path] = {}
240+
}
241+
last = val
242+
i++
243+
}
244+
last = compiled
245+
}
246+
})
247+
}
248+
207249
/**
208250
* Composer
209251
*

src/core/context.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MessageFunction } from '../message/compiler'
1+
import { MessageFunction } from '../message/runtime'
22
import {
33
LinkedModifiers,
44
PluralizationRules,

src/core/translate.ts

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { Path, resolveValue, PathValue } from '../path'
22
import { CompileOptions } from '../message/options'
33
import { CompileError } from '../message/errors'
4-
import { compile, MessageFunction } from '../message/compiler'
4+
import { compile } from '../message/compiler'
55
import {
66
createMessageContext,
77
NamedValue,
8+
MessageFunction,
89
MessageContextOptions
910
} from '../message/runtime'
1011
import {
@@ -25,6 +26,7 @@ import {
2526
isArray,
2627
isPlainObject,
2728
isEmptyObject,
29+
generateFormatCacheKey,
2830
generateCodeFrame
2931
} from '../utils'
3032

@@ -232,23 +234,33 @@ export function translate(
232234
return unresolving ? NOT_REOSLVED : key
233235
}
234236

235-
// compile message format
237+
// setup compile error detecting
236238
let occured = false
237239
const errorDetector = () => {
238240
occured = true
239241
}
240-
const msg = isMessageFunction(format)
241-
? format
242-
: compile(
242+
243+
// compile message format
244+
let msg
245+
if (!isMessageFunction(format)) {
246+
msg = compile(
247+
format,
248+
getCompileOptions(
249+
targetLocale,
250+
cacheBaseKey,
243251
format,
244-
getCompileOptions(
245-
targetLocale,
246-
cacheBaseKey,
247-
format,
248-
warnHtmlMessage,
249-
errorDetector
250-
)
252+
warnHtmlMessage,
253+
errorDetector
251254
)
255+
)
256+
msg.locale = targetLocale
257+
msg.key = key
258+
msg.source = format
259+
} else {
260+
msg = format
261+
msg.locale = msg.locale || targetLocale
262+
msg.key = msg.key || key
263+
}
252264

253265
// if occured compile error, return the message format
254266
if (occured) {
@@ -326,7 +338,8 @@ function getCompileOptions(
326338
throw err
327339
}
328340
},
329-
onCacheKey: (source: string): string => `{${locale}}{${key}}{${source}}`
341+
onCacheKey: (source: string): string =>
342+
generateFormatCacheKey(locale, key, source)
330343
} as CompileOptions
331344
}
332345

src/index.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,19 @@
1+
export { generateFormatCacheKey, friendlyJSONstringify } from './utils'
12
export { Path, PathValue } from './path'
23
export { createParser, Parser } from './message/parser'
3-
export { CompileOptions } from './message/options'
4-
export { PluralizationRule, LinkedModifiers } from './message/runtime'
4+
export {
5+
CompileError,
6+
CompileDomain,
7+
CompileErrorCodes
8+
} from './message/errors'
9+
export { CompileOptions, CompileErrorHandler } from './message/options'
10+
export { baseCompile, compile } from './message/compiler'
11+
export {
12+
MessageFunction,
13+
MessageFunctions,
14+
PluralizationRule,
15+
LinkedModifiers
16+
} from './message/runtime'
517
export {
618
Locale,
719
FallbackLocale,

src/message/compiler.ts

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { createParser, ResourceNode } from './parser'
33
import { transform } from './transformer'
44
import { generate } from './generator'
55
import { CompileError, defaultOnError } from './errors'
6+
import { MessageFunction, MessageFunctions } from './runtime'
67
import { warn, isBoolean } from '../utils'
78

89
export type CompileResult = {
@@ -17,8 +18,6 @@ export type Compiler = Readonly<{
1718
compile: (source: string, options?: CompileOptions) => CompileResult
1819
}>
1920

20-
export type MessageFunction = (ctx: unknown) => unknown
21-
2221
// TODO: This code should be removed with using rollup (`/*#__PURE__*/`)
2322
const RE_HTML_TAG = /<\/?[\w\s="/.':;#-\/]+>/
2423
function checkHtmlMessage(source: string, options: CompileOptions): void {
@@ -33,7 +32,24 @@ function checkHtmlMessage(source: string, options: CompileOptions): void {
3332
}
3433

3534
const defaultOnCacheKey = (source: string): string => source
36-
const compileCache: Record<string, MessageFunction> = Object.create(null)
35+
const compileCache: MessageFunctions = Object.create(null)
36+
37+
export function baseCompile(
38+
source: string,
39+
options: CompileOptions = {}
40+
): CompileResult {
41+
// parse source codes
42+
const parser = createParser({ ...options })
43+
const ast = parser.parse(source)
44+
45+
// transform ASTs
46+
transform(ast, { ...options })
47+
48+
// generate javascript codes
49+
const code = generate(ast, { ...options })
50+
51+
return { ast, code }
52+
}
3753

3854
export function compile(
3955
source: string,
@@ -58,16 +74,10 @@ export function compile(
5874
onError(err)
5975
}
6076

61-
// parse source codes
62-
const parser = createParser({ ...options })
63-
const ast = parser.parse(source)
64-
65-
// transform ASTs
66-
transform(ast, { ...options })
67-
68-
// generate javascript codes
69-
const code = generate(ast, { ...options })
77+
// compile
78+
const { code } = baseCompile(source, options)
7079

80+
// evaluate function
7181
const msg = new Function(`return ${code}`)() as MessageFunction
7282

7383
// if occured compile error, don't cache

src/message/runtime.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,13 @@ export type PluralizationRule = (
1616
export type PluralizationRules = { [locale: string]: PluralizationRule }
1717
export type LinkedModify = (value: unknown, type: string) => unknown
1818
export type LinkedModifiers = { [key: string]: LinkedModify }
19-
export type MessageFunction = (ctx: MessageContext) => unknown
20-
export type MessageFucntions = { [key: string]: MessageFunction }
19+
export type MessageFunction = {
20+
(ctx: MessageContext): unknown
21+
key?: string
22+
locale?: string
23+
source?: string
24+
}
25+
export type MessageFunctions = Record<string, MessageFunction>
2126
export type MessageResolveFunction = (key: string) => MessageFunction
2227
export type NamedValue<T = {}> = T & { [prop: string]: unknown }
2328
export type MessageNormalize = (values: unknown[]) => unknown
@@ -37,7 +42,7 @@ export type MessageContextOptions<N = {}> = {
3742
modifiers?: LinkedModifiers
3843
pluralIndex?: number
3944
pluralRules?: PluralizationRules
40-
messages?: MessageFucntions | MessageResolveFunction // TODO: need to design resolve message function?
45+
messages?: MessageFunctions | MessageResolveFunction // TODO: need to design resolve message function?
4146
processor?: MessageProcessor
4247
}
4348

src/utils.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,18 @@
66
export const generateSymbolID = (): string =>
77
`vue-i18n-${new Date().getUTCMilliseconds().toString()}`
88

9+
export const generateFormatCacheKey = (
10+
locale: string,
11+
key: string,
12+
source: string
13+
): string => friendlyJSONstringify({ l: locale, k: key, s: source })
14+
15+
export const friendlyJSONstringify = (json: unknown): string =>
16+
JSON.stringify(json)
17+
.replace(/\u2028/g, '\\u2028')
18+
.replace(/\u2029/g, '\\u2029')
19+
.replace(/\u0027/g, '\\u0027')
20+
921
export const isDate = (val: unknown): val is Date =>
1022
toTypeString(val) === '[object Date]'
1123

test/message/compiler.test.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* eslint-disable @typescript-eslint/no-empty-function */
1+
/* eslint-disable @typescript-eslint/no-empty-function, no-irregular-whitespace */
22

33
// utils
44
jest.mock('../../src/utils', () => ({
@@ -9,12 +9,10 @@ import { warn } from '../../src/utils'
99

1010
import { compile } from '../../src/message/compiler'
1111

12-
/* eslint-disable no-irregular-whitespace */
1312
test(`@.caml:{'no apples'} | {0} apple | {n} apples`, () => {
1413
const code = compile(`@.caml:{'no apples'} | {0} apple | {n} apples`)
1514
expect(code.toString()).toMatchSnapshot('code')
1615
})
17-
/* eslint-enable no-irregular-whitespace */
1816

1917
describe('warnHtmlMessage', () => {
2018
test('default', () => {
@@ -50,4 +48,4 @@ describe('edge cases', () => {
5048
})
5149
})
5250

53-
/* eslint-enable @typescript-eslint/no-empty-function */
51+
/* eslint-enable @typescript-eslint/no-empty-function, no-irregular-whitespace */

0 commit comments

Comments
 (0)