Skip to content

Commit

Permalink
⭐ new: component interpolation
Browse files Browse the repository at this point in the history
Closes #145, #144, #37
  • Loading branch information
kazupon committed May 2, 2017
1 parent a8c046d commit 23f7d34
Show file tree
Hide file tree
Showing 5 changed files with 270 additions and 16 deletions.
5 changes: 3 additions & 2 deletions decls/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ declare type NumberFormatOptions = {
declare type NumberFormat = { [key: string]: NumberFormatOptions };
declare type NumberFormats = { [key: Locale]: NumberFormat };

declare type TranslateResult = string | Array<string>;
declare type TranslateResult = string | Array<any>;
declare type DateTimeFormatResult = string;
declare type NumberFormatResult = string;
declare type MissingHandler = (locale: Locale, key: Path, vm?: any) => void;
Expand Down Expand Up @@ -90,6 +90,7 @@ declare interface I18n {
setLocaleMessage (locale: Locale, message: LocaleMessage): void,
mergeLocaleMessage (locale: Locale, message: LocaleMessage): void,
t (key: Path, ...values: any): TranslateResult,
i (key: Path, ...values: any): TranslateResult,
tc (key: Path, choice?: number, ...values: any): TranslateResult,
te (key: Path, locale?: Locale): boolean,
getDateTimeFormat (locale: Locale): DateTimeFormat,
Expand All @@ -105,5 +106,5 @@ declare interface I18n {
declare type FormatterOptions = { [key: string]: any };

declare interface Formatter {
format (message: string, ...values: any): string
format (message: string, values: any): any
};
33 changes: 33 additions & 0 deletions src/component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/* @flow */

import { warn } from './util'

export default {
name: 'i18n',
functional: true,
props: {
path: {
type: String,
required: true
},
locale: {
type: String
}
},
render (h: Function, { props, children, parent }: Object) {
const i18n = parent.$i18n
if (!i18n) {
warn('Cannot find VueI18n instance!')
return children
}

const path: Path = props.path
const locale: ?Locale = props.locale

const params: Array<any> = []
locale && params.push(locale)
children.forEach(child => params.push(child))

return i18n.i(path, ...params)
}
}
85 changes: 71 additions & 14 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export default class VueI18n {
const messages: LocaleMessages = options.messages || {}
const dateTimeFormats = options.dateTimeFormats || {}
const numberFormats = options.numberFormats || {}

this._vm = null
this._formatter = options.formatter || new BaseFormatter()
this._missing = options.missing || null
Expand Down Expand Up @@ -160,7 +161,12 @@ export default class VueI18n {
return !val && !isNull(this._root) && this._fallbackRoot
}

_interpolate (message: LocaleMessageObject, key: Path, values: any): any {
_interpolate (
message: LocaleMessageObject,
key: Path,
interpolateMode: string,
values: any
): any {
if (!message) { return null }

const pathRet: PathValue = getPathValue(message, key)
Expand Down Expand Up @@ -197,26 +203,38 @@ export default class VueI18n {
// them with its translation
const matches: any = ret.match(/(@:[\w|.]+)/g)
for (const idx in matches) {
const link = matches[idx]
const link: string = matches[idx]
// Remove the leading @:
const linkPlaceholder = link.substr(2)
const linkPlaceholder: string = link.substr(2)
// Translate the link
const translatedstring = this._interpolate(message, linkPlaceholder, values)
// Replace the link with the translated string
ret = ret.replace(link, translatedstring)
const translated: any = this._interpolate(message, linkPlaceholder, interpolateMode, values)
if (interpolateMode === 'raw') {
return translated
}
// Replace the link with the translated
ret = ret.replace(link, translated)
}
}

return !values ? ret : this._format(ret, values)
return !values ? ret : this._render(ret, interpolateMode, values)
}

_format (message: string, ...values: any): string {
return this._formatter.format(message, ...values)
_render (message: string, interpolateMode: string, values: any): any {
const ret = this._formatter.format(message, values)
// if interpolateMode is **not** 'string' ('row'),
// return the compiled data (e.g. ['foo', VNode, 'bar']) with formatter
return interpolateMode === 'string' ? ret.join('') : ret
}

_translate (messages: LocaleMessages, locale: Locale, fallback: Locale, key: Path, args: any): any {
let res: any = null
res = this._interpolate(messages[locale], key, args)
_translate (
messages: LocaleMessages,
locale: Locale,
fallback: Locale,
key: Path,
interpolateMode: string,
args: any
): any {
let res: any = this._interpolate(messages[locale], key, interpolateMode, args)
if (!isNull(res)) { return res }

res = this._interpolate(messages[fallback], key, args)
Expand All @@ -236,7 +254,7 @@ export default class VueI18n {
const parsedArgs = parseArgs(...values)
const locale: Locale = parsedArgs.locale || _locale

const ret: any = this._translate(messages, locale, this.fallbackLocale, key, parsedArgs.params)
const ret: any = this._translate(messages, locale, this.fallbackLocale, key, 'string', parsedArgs.params)
if (this._isFallbackRoot(ret)) {
if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn) {
warn(`Fall back to translate the keypath '${key}' with root locale.`)
Expand All @@ -252,7 +270,46 @@ export default class VueI18n {
return this._t(key, this.locale, this.messages, null, ...values)
}

_tc (key: Path, _locale: Locale, messages: LocaleMessages, host: any, choice?: number, ...values: any): any {
_i (key: Path, locale: Locale, messages: LocaleMessages, host: any, ...values: any): any {
const ret: any =
this._translate(messages, locale, this.fallbackLocale, key, 'raw', values)
if (this._isFallbackRoot(ret)) {
if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn) {
warn(`Fall back to interpolate the keypath '${key}' with root locale.`)
}
if (!this._root) { throw Error('unexpected error') }
return this._root.i(key, ...values)
} else {
return this._warnDefault(locale, key, ret, host)
}
}

i (key: Path, ...values: any): TranslateResult {
if (!key) { return '' }

let locale: Locale = this.locale
let index: number = 0
if (typeof values[0] === 'string') {
locale = values[0]
index = 1
}

const params: Array<any> = []
for (let i = index; i < values.length; i++) {
params.push(values[i])
}

return this._i(key, locale, this.messages, null, ...params)
}

_tc (
key: Path,
_locale: Locale,
messages: LocaleMessages,
host: any,
choice?: number,
...values: any
): any {
if (!key) { return '' }
if (choice !== undefined) {
return fetchChoice(this._t(key, _locale, messages, host, ...values), choice)
Expand Down
2 changes: 2 additions & 0 deletions src/install.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { warn } from './util'
import extend from './extend'
import mixin from './mixin'
import component from './component'

export let Vue

Expand All @@ -25,6 +26,7 @@ export function install (_Vue) {

extend(Vue)
Vue.mixin(mixin)
Vue.component(component.name, component)

// use object-based merge strategy
const strats = Vue.config.optionMergeStrategies
Expand Down
161 changes: 161 additions & 0 deletions test/unit/interpolation.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
const messages = {
en: {
text: 'one: {0}',
premitive: 'one: {0}, two: {1}',
component: 'root: {0}, component: {1}',
link: '@:premitive'
},
ja: {
text: '一: {0}',
}
}
const components = {
comp: {
props: {
msg: { type: String, default: '' }
},
render (h) {
return h('p', [this.msg])
}
}
}

describe('component interpolation', () => {
let i18n
beforeEach(() => {
i18n = new VueI18n({
locale: 'en',
messages
})
})

describe('children', () => {
describe('text nodes', () => {
it('should be interpolated', done => {
const el = document.createElement('div')
const vm = new Vue({
i18n,
render (h) {
return h('p', {}, [
h('i18n', { props: { path: 'text' } }, [
this._v('1')
])
])
}
}).$mount(el)
nextTick(() => {
assert.equal(vm.$el.textContent, 'one: 1')
}).then(done)
})
})

describe('premitive nodes', () => {
it('should be interpolated', done => {
const el = document.createElement('div')
const vm = new Vue({
i18n,
render (h) {
return h('div', {}, [
h('i18n', { props: { path: 'premitive' } }, [
h('p', ['1']),
h('p', ['2'])
])
])
}
}).$mount(el)
nextTick(() => {
assert.equal(vm.$el.innerHTML, 'one: <p>1</p>, two: <p>2</p>')
}).then(done)
})
})

describe('components', () => {
it('should be interpolated', done => {
const el = document.createElement('div')
const vm = new Vue({
i18n,
components,
render (h) {
return h('div', {}, [
h('i18n', { props: { path: 'component' } }, [
h('p', ['1']),
h('comp', { props: { msg: 'foo' } })
])
])
}
}).$mount(el)
nextTick(() => {
assert.equal(vm.$el.innerHTML, 'root: <p>1</p>, component: <p>foo</p>')
}).then(done)
})
})

describe('nested components', () => {
it('should be interpolated', done => {
const el = document.createElement('div')
const vm = new Vue({
i18n,
components,
render (h) {
return h('div', {}, [
h('i18n', { props: { path: 'component' } }, [
h('p', ['1']),
h('div', {}, [
h('i18n', { props: { path: 'component' } }, [
h('p', ['2']),
h('comp', { props: { msg: 'nested' } })
])
])
])
])
}
}).$mount(el)
nextTick(() => {
assert.equal(
vm.$el.innerHTML,
'root: <p>1</p>, component: <div>root: <p>2</p>, component: <p>nested</p></div>'
)
}).then(done)
})
})
})

describe('linked', () => {
it('should be interpolated', done => {
const el = document.createElement('div')
const vm = new Vue({
i18n,
render (h) {
return h('p', {}, [
h('i18n', { props: { path: 'link' } }, [
h('p', ['1']),
h('p', ['2'])
])
])
}
}).$mount(el)
nextTick(() => {
assert.equal(vm.$el.innerHTML, 'one: <p>1</p>, two: <p>2</p>')
}).then(done)
})
})

describe('locale', () => {
it('should be interpolated', done => {
const el = document.createElement('div')
const vm = new Vue({
i18n,
render (h) {
return h('p', {}, [
h('i18n', { props: { path: 'text', locale: 'ja' } }, [
this._v('1')
])
])
}
}).$mount(el)
nextTick(() => {
assert.equal(vm.$el.textContent, '一: 1')
}).then(done)
})
})
})

0 comments on commit 23f7d34

Please sign in to comment.