Skip to content

Commit

Permalink
Add support for flat json separated with dot('.') (#402)
Browse files Browse the repository at this point in the history
1. add function `handleFlatJson` to `message-resolver`,
function `handleFlatJson` is use to transform flat json to normal json.

2. add optional support for flat json messages to `vue-i18n`.

3. add some tests to `massage-resolver` and `vue-i18n`.

4. move function `hasOwn` to `@intlify/shared`.
  • Loading branch information
PeterAlfredLee committed Mar 18, 2021
1 parent 260e35b commit 34dac6b
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 10 deletions.
49 changes: 48 additions & 1 deletion packages/message-resolver/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isObject } from '@intlify/shared'
import { isObject, hasOwn } from '@intlify/shared'

/** @VueI18nGeneral */
export type Path = string
Expand Down Expand Up @@ -321,3 +321,50 @@ export function resolveValue(obj: unknown, path: Path): PathValue {

return last
}

/**
* Transform flat json in obj to normal json in obj
*/
export function handleFlatJson(obj: unknown): unknown {
// check obj
if (!isObject(obj)) {
return obj
}

for (const key in obj as object) {
// check key
if (!hasOwn(obj, key)) {
continue
}

// handle for normal json
if (!key.includes(PathCharTypes.DOT)) {
// recursive process value if value is also a object
if (isObject(obj[key])) {
handleFlatJson(obj[key])
}
}
// handle for flat json, transform to normal json
else {
// go to the last object
const subKeys = key.split(PathCharTypes.DOT)
const lastIndex = subKeys.length - 1
let currentObj = obj
for (let i = 0; i < lastIndex; i++) {
if (!(subKeys[i] in currentObj)) {
currentObj[subKeys[i]] = {}
}
currentObj = currentObj[subKeys[i]]
}
// update last object value, delete old property
currentObj[subKeys[lastIndex]] = obj[key]
delete obj[key]
// recursive process value if value is also a object
if (isObject(currentObj[subKeys[lastIndex]])) {
handleFlatJson(currentObj[subKeys[lastIndex]])
}
}
}

return obj
}
28 changes: 27 additions & 1 deletion packages/message-resolver/test/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { parse, resolveValue } from '../src/index'
import { parse, resolveValue, handleFlatJson } from '../src/index'

test('parse', () => {
expect(parse('a')).toEqual(['a'])
Expand Down Expand Up @@ -121,3 +121,29 @@ test('resolveValue', () => {
// blanket middle
expect(resolveValue({}, 'a.b.c[]d')).toEqual(null)
})

test('handleFlatJson', () => {
const obj = {
a: { a1: 'a1.value' },
'a.a2': 'a.a2.value',
'b.x': {
'b1.x': 'b1.x.value',
'b2.x': ['b2.x.value0', 'b2.x.value1'],
'b3.x': { 'b3.x': 'b3.x.value' }
}
}
const expectObj = {
a: {
a1: 'a1.value',
a2: 'a.a2.value'
},
b: {
x: {
b1: { x: 'b1.x.value' },
b2: { x: ['b2.x.value0', 'b2.x.value1'] },
b3: { x: { b3: { x: 'b3.x.value' } } }
}
}
}
expect(handleFlatJson(obj)).toEqual(expectObj)
})
5 changes: 5 additions & 0 deletions packages/shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ export function escapeHtml(rawText: string): string {
.replace(/'/g, '&apos;')
}

const hasOwnProperty = Object.prototype.hasOwnProperty
export function hasOwn(obj: object | Array<any>, key: string): boolean {
return hasOwnProperty.call(obj, key)
}

/* eslint-enable */

/**
Expand Down
29 changes: 21 additions & 8 deletions packages/vue-i18n/src/composer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import {
isBoolean,
isPlainObject,
makeSymbol,
isObject
isObject,
hasOwn
} from '@intlify/shared'
import {
isTranslateFallbackWarn,
Expand All @@ -34,7 +35,8 @@ import {
parseNumberArgs,
clearNumberFormat,
NOT_REOSLVED,
DevToolsTimelineEvents
DevToolsTimelineEvents,
handleFlatJson
} from '@intlify/core-base'
import { I18nWarnCodes, getWarnMessage } from './warnings'
import { I18nErrorCodes, createI18nError } from './errors'
Expand Down Expand Up @@ -161,6 +163,13 @@ export interface ComposerOptions<Message = VueMessageType> {
* @defaultValue `{}`
*/
messages?: LocaleMessages<Message>
/**
* @remarks
* Allow use flat json messages or not
*
* @defaultValue `false`
*/
flatJson?: boolean
/**
* @remarks
* The datetime formats of localization.
Expand Down Expand Up @@ -936,6 +945,7 @@ function defineCoreMissingHandler<Message = VueMessageType>(
type GetLocaleMessagesOptions<Message = VueMessageType> = {
messages?: LocaleMessages<Message>
__i18n?: CustomBlocks<Message>
flatJson?: boolean
}

export function getLocaleMessages<Message = VueMessageType>(
Expand Down Expand Up @@ -963,13 +973,16 @@ export function getLocaleMessages<Message = VueMessageType>(
})
}

return ret
}
// handle messages for flat json
if (options.flatJson) {
for (const key in ret) {
if (hasOwn(ret, key)) {
handleFlatJson(ret[key])
}
}
}

const hasOwnProperty = Object.prototype.hasOwnProperty
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function hasOwn(obj: object | Array<any>, key: string): boolean {
return hasOwnProperty.call(obj, key)
return ret
}

const isNotObjectOrIsArray = (val: unknown) => !isObject(val) || isArray(val)
Expand Down
9 changes: 9 additions & 0 deletions packages/vue-i18n/src/legacy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,13 @@ export interface VueI18nOptions {
* @defaultValue `{}`
*/
messages?: LocaleMessages<VueMessageType>
/**
* @remarks
* Allow use flat json messages or not
*
* @defaultValue `false`
*/
flatJson?: boolean
/**
* @remarks
* The datetime formats of localization.
Expand Down Expand Up @@ -964,11 +971,13 @@ function convertComposerOptions<

const datetimeFormats = options.datetimeFormats
const numberFormats = options.numberFormats
const flatJson = options.flatJson

return {
locale,
fallbackLocale,
messages,
flatJson,
datetimeFormats,
numberFormats,
missing,
Expand Down
27 changes: 27 additions & 0 deletions packages/vue-i18n/test/i18n.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,33 @@ describe('createI18n', () => {
})
})

describe('createI18n with flat json messages', () => {
test('legacy mode', () => {
const i18n = createI18n({
flatJson: true,
messages: {
en: { 'mainMenu.buttonStart': 'Start!' }
}
})
const messages = i18n.global.messages
// @ts-ignore
expect(messages.en.mainMenu.buttonStart).toEqual('Start!')
})

test('composition mode', () => {
const i18n = createI18n({
legacy: false,
flatJson: true,
messages: {
en: { 'mainMenu.buttonStart': 'Start!' }
}
})
const messages = i18n.global.messages.value
// @ts-ignore
expect(messages.en.mainMenu.buttonStart).toEqual('Start!')
})
})

describe('useI18n', () => {
let org: any // eslint-disable-line @typescript-eslint/no-explicit-any
let spy: any // eslint-disable-line @typescript-eslint/no-explicit-any
Expand Down

0 comments on commit 34dac6b

Please sign in to comment.