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 function #972

Merged
merged 2 commits into from
Aug 13, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
7 changes: 6 additions & 1 deletion decls/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@ declare var Intl: any;

declare type Path = string;
declare type Locale = string;
declare type MessageContext = {
list: (index: number) => mixed,
named: (key: string) => mixed
}
declare type MessageFunction = (ctx: MessageContext) => string
declare type FallbackLocale = string | string[] | false | { [locale: string]: string[] };
declare type LocaleMessage = string | LocaleMessageObject | LocaleMessageArray;
declare type LocaleMessage = string | MessageFunction | LocaleMessageObject | LocaleMessageArray;
declare type LocaleMessageObject = { [key: Path]: LocaleMessage };
declare type LocaleMessageArray = Array<LocaleMessage>;
declare type LocaleMessages = { [key: Locale]: LocaleMessageObject };
Expand Down
34 changes: 25 additions & 9 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
isArray,
isBoolean,
isString,
isFunction,
looseClone,
remove,
includes,
Expand Down Expand Up @@ -188,7 +189,7 @@ export default class VueI18n {
paths.pop()
}
})
} else if (Array.isArray(message)) {
} else if (isArray(message)) {
message.forEach((item, index) => {
if (isPlainObject(item)) {
paths.push(`[${index}]`)
Expand Down Expand Up @@ -382,16 +383,16 @@ export default class VueI18n {
if (!message) { return null }

const pathRet: PathValue = this._path.getPathValue(message, key)
if (Array.isArray(pathRet) || isPlainObject(pathRet)) { return pathRet }
if (isArray(pathRet) || isPlainObject(pathRet)) { return pathRet }

let ret: mixed
if (isNull(pathRet)) {
/* istanbul ignore else */
if (isPlainObject(message)) {
ret = message[key]
if (!isString(ret)) {
if (!(isString(ret) || isFunction(ret))) {
if (process.env.NODE_ENV !== 'production' && !this._isSilentTranslationWarn(key) && !this._isSilentFallback(locale, key)) {
warn(`Value of key '${key}' is not a string!`)
warn(`Value of key '${key}' is not a string or function !`)
}
return null
}
Expand All @@ -400,18 +401,18 @@ export default class VueI18n {
}
} else {
/* istanbul ignore else */
if (isString(pathRet)) {
if (isString(pathRet) || isFunction(pathRet)) {
ret = pathRet
} else {
if (process.env.NODE_ENV !== 'production' && !this._isSilentTranslationWarn(key) && !this._isSilentFallback(locale, key)) {
warn(`Value of key '${key}' is not a string!`)
warn(`Value of key '${key}' is not a string or function!`)
}
return null
}
}

// Check for the existence of links within the translated string
if (ret.indexOf('@:') >= 0 || ret.indexOf('@.') >= 0) {
if (isString(ret) && (ret.indexOf('@:') >= 0 || ret.indexOf('@.') >= 0)) {
ret = this._link(locale, message, ret, host, 'raw', values, visitedLinkStack)
}

Expand Down Expand Up @@ -476,7 +477,7 @@ export default class VueI18n {
}
translated = this._warnDefault(
locale, linkPlaceholder, translated, host,
Array.isArray(values) ? values : [values],
isArray(values) ? values : [values],
interpolateMode
)

Expand All @@ -495,7 +496,22 @@ export default class VueI18n {
return ret
}

_render (message: string, interpolateMode: string, values: any, path: string): any {
_createMessageContext (values: any): MessageContext {
const _list = isArray(values) ? values : []
const _named = isObject(values) ? values : {}
const list = (index: number): mixed => _list[index]
const named = (key: string): mixed => _named[key]
return {
list,
named
}
}

_render (message: string | MessageFunction, interpolateMode: string, values: any, path: string): any {
if (isFunction(message)) {
return message(this._createMessageContext(values))
}

let ret = this._formatter.interpolate(message, values, path)

// If the custom formatter refuses to work - apply the default one
Expand Down
2 changes: 1 addition & 1 deletion src/path.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ function parse (path: Path): ?Array<string> {
}
}

export type PathValue = PathValueObject | PathValueArray | string | number | boolean | null
export type PathValue = PathValueObject | PathValueArray | Function | string | number | boolean | null
export type PathValueObject = { [key: string]: PathValue }
export type PathValueArray = Array<PathValue>

Expand Down
12 changes: 8 additions & 4 deletions src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,15 @@ export function isNull (val: mixed): boolean {
return val === null || val === undefined
}

export function isFunction (val: mixed): boolean %checks {
return typeof val === 'function'
}

export function parseArgs (...args: Array<mixed>): Object {
let locale: ?string = null
let params: mixed = null
if (args.length === 1) {
if (isObject(args[0]) || Array.isArray(args[0])) {
if (isObject(args[0]) || isArray(args[0])) {
params = args[0]
} else if (typeof args[0] === 'string') {
locale = args[0]
Expand All @@ -81,7 +85,7 @@ export function parseArgs (...args: Array<mixed>): Object {
locale = args[0]
}
/* istanbul ignore if */
if (isObject(args[1]) || Array.isArray(args[1])) {
if (isObject(args[1]) || isArray(args[1])) {
params = args[1]
}
}
Expand Down Expand Up @@ -137,8 +141,8 @@ export function looseEqual (a: any, b: any): boolean {
const isObjectB: boolean = isObject(b)
if (isObjectA && isObjectB) {
try {
const isArrayA: boolean = Array.isArray(a)
const isArrayB: boolean = Array.isArray(b)
const isArrayA: boolean = isArray(a)
const isArrayB: boolean = isArray(b)
if (isArrayA && isArrayB) {
return a.length === b.length && a.every((e: any, i: number): boolean => {
return looseEqual(e, b[i])
Expand Down
55 changes: 55 additions & 0 deletions test/unit/message_function.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
describe('message function', () => {
describe('simple', () => {
it('should be worked', () => {
i18n = new VueI18n({
locale: 'ja',
fallbackLocale: 'en',
messages: {
en: {
hello: (ctx) => 'hello'
},
ja: {
hello: (ctx) => 'こんにちは!'
}
}
})
assert.strictEqual(i18n.t('hello'), 'こんにちは!')
})
})

describe('list argument', () => {
it('should be worked', () => {
i18n = new VueI18n({
locale: 'ja',
fallbackLocale: 'en',
messages: {
en: {
hello: (ctx) => `hello, ${ctx.list(0)}!`
},
ja: {
hello: (ctx) => `こんにちは、${ctx.list(0)}!`
}
}
})
assert.strictEqual(i18n.t('hello', ['kazupon']), 'こんにちは、kazupon!')
})
})

describe('named argument', () => {
it('should be worked', () => {
i18n = new VueI18n({
locale: 'ja',
fallbackLocale: 'en',
messages: {
en: {
hello: (ctx) => `hello, ${ctx.named('name')}!`
},
ja: {
hello: (ctx) => `こんにちは、${ctx.named('name')}!`
}
}
})
assert.strictEqual(i18n.t('hello', { name: 'kazupon' }), 'こんにちは、kazupon!')
})
})
})
9 changes: 8 additions & 1 deletion types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ declare namespace VueI18n {
type FallbackLocale = string | string[] | false | { [locale: string]: string[] }
type Values = any[] | { [key: string]: any };
type Choice = number;
type LocaleMessage = string | LocaleMessageObject | LocaleMessageArray;
interface MessageContext {
list(index: number): unknown
named(key: string): unknown
}
type MessageFunction = (ctx: MessageContext) => string;
type LocaleMessage = string | MessageFunction | LocaleMessageObject | LocaleMessageArray;
interface LocaleMessageObject { [key: string]: LocaleMessage; }
interface LocaleMessageArray { [index: number]: LocaleMessage; }
interface LocaleMessages { [key: string]: LocaleMessageObject; }
Expand Down Expand Up @@ -123,6 +128,8 @@ export type Locale = VueI18n.Locale;
export type FallbackLocale = VueI18n.FallbackLocale;
export type Values = VueI18n.Values;
export type Choice = VueI18n.Choice;
export type MessageContext = VueI18n.MessageContext;
export type MessageFunction = VueI18n.MessageFunction;
export type LocaleMessage = VueI18n.LocaleMessage;
export type LocaleMessageObject = VueI18n.LocaleMessageObject;
export type LocaleMessageArray = VueI18n.LocaleMessageArray;
Expand Down