Skip to content
This repository was archived by the owner on Dec 31, 2024. It is now read-only.

Commit 23f7d34

Browse files
committed
⭐ new: component interpolation
Closes #145, #144, #37
1 parent a8c046d commit 23f7d34

5 files changed

Lines changed: 270 additions & 16 deletions

File tree

decls/i18n.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ declare type NumberFormatOptions = {
4545
declare type NumberFormat = { [key: string]: NumberFormatOptions };
4646
declare type NumberFormats = { [key: Locale]: NumberFormat };
4747

48-
declare type TranslateResult = string | Array<string>;
48+
declare type TranslateResult = string | Array<any>;
4949
declare type DateTimeFormatResult = string;
5050
declare type NumberFormatResult = string;
5151
declare type MissingHandler = (locale: Locale, key: Path, vm?: any) => void;
@@ -90,6 +90,7 @@ declare interface I18n {
9090
setLocaleMessage (locale: Locale, message: LocaleMessage): void,
9191
mergeLocaleMessage (locale: Locale, message: LocaleMessage): void,
9292
t (key: Path, ...values: any): TranslateResult,
93+
i (key: Path, ...values: any): TranslateResult,
9394
tc (key: Path, choice?: number, ...values: any): TranslateResult,
9495
te (key: Path, locale?: Locale): boolean,
9596
getDateTimeFormat (locale: Locale): DateTimeFormat,
@@ -105,5 +106,5 @@ declare interface I18n {
105106
declare type FormatterOptions = { [key: string]: any };
106107

107108
declare interface Formatter {
108-
format (message: string, ...values: any): string
109+
format (message: string, values: any): any
109110
};

src/component.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/* @flow */
2+
3+
import { warn } from './util'
4+
5+
export default {
6+
name: 'i18n',
7+
functional: true,
8+
props: {
9+
path: {
10+
type: String,
11+
required: true
12+
},
13+
locale: {
14+
type: String
15+
}
16+
},
17+
render (h: Function, { props, children, parent }: Object) {
18+
const i18n = parent.$i18n
19+
if (!i18n) {
20+
warn('Cannot find VueI18n instance!')
21+
return children
22+
}
23+
24+
const path: Path = props.path
25+
const locale: ?Locale = props.locale
26+
27+
const params: Array<any> = []
28+
locale && params.push(locale)
29+
children.forEach(child => params.push(child))
30+
31+
return i18n.i(path, ...params)
32+
}
33+
}

src/index.js

Lines changed: 71 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export default class VueI18n {
4141
const messages: LocaleMessages = options.messages || {}
4242
const dateTimeFormats = options.dateTimeFormats || {}
4343
const numberFormats = options.numberFormats || {}
44+
4445
this._vm = null
4546
this._formatter = options.formatter || new BaseFormatter()
4647
this._missing = options.missing || null
@@ -160,7 +161,12 @@ export default class VueI18n {
160161
return !val && !isNull(this._root) && this._fallbackRoot
161162
}
162163

163-
_interpolate (message: LocaleMessageObject, key: Path, values: any): any {
164+
_interpolate (
165+
message: LocaleMessageObject,
166+
key: Path,
167+
interpolateMode: string,
168+
values: any
169+
): any {
164170
if (!message) { return null }
165171

166172
const pathRet: PathValue = getPathValue(message, key)
@@ -197,26 +203,38 @@ export default class VueI18n {
197203
// them with its translation
198204
const matches: any = ret.match(/(@:[\w|.]+)/g)
199205
for (const idx in matches) {
200-
const link = matches[idx]
206+
const link: string = matches[idx]
201207
// Remove the leading @:
202-
const linkPlaceholder = link.substr(2)
208+
const linkPlaceholder: string = link.substr(2)
203209
// Translate the link
204-
const translatedstring = this._interpolate(message, linkPlaceholder, values)
205-
// Replace the link with the translated string
206-
ret = ret.replace(link, translatedstring)
210+
const translated: any = this._interpolate(message, linkPlaceholder, interpolateMode, values)
211+
if (interpolateMode === 'raw') {
212+
return translated
213+
}
214+
// Replace the link with the translated
215+
ret = ret.replace(link, translated)
207216
}
208217
}
209218

210-
return !values ? ret : this._format(ret, values)
219+
return !values ? ret : this._render(ret, interpolateMode, values)
211220
}
212221

213-
_format (message: string, ...values: any): string {
214-
return this._formatter.format(message, ...values)
222+
_render (message: string, interpolateMode: string, values: any): any {
223+
const ret = this._formatter.format(message, values)
224+
// if interpolateMode is **not** 'string' ('row'),
225+
// return the compiled data (e.g. ['foo', VNode, 'bar']) with formatter
226+
return interpolateMode === 'string' ? ret.join('') : ret
215227
}
216228

217-
_translate (messages: LocaleMessages, locale: Locale, fallback: Locale, key: Path, args: any): any {
218-
let res: any = null
219-
res = this._interpolate(messages[locale], key, args)
229+
_translate (
230+
messages: LocaleMessages,
231+
locale: Locale,
232+
fallback: Locale,
233+
key: Path,
234+
interpolateMode: string,
235+
args: any
236+
): any {
237+
let res: any = this._interpolate(messages[locale], key, interpolateMode, args)
220238
if (!isNull(res)) { return res }
221239

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

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

255-
_tc (key: Path, _locale: Locale, messages: LocaleMessages, host: any, choice?: number, ...values: any): any {
273+
_i (key: Path, locale: Locale, messages: LocaleMessages, host: any, ...values: any): any {
274+
const ret: any =
275+
this._translate(messages, locale, this.fallbackLocale, key, 'raw', values)
276+
if (this._isFallbackRoot(ret)) {
277+
if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn) {
278+
warn(`Fall back to interpolate the keypath '${key}' with root locale.`)
279+
}
280+
if (!this._root) { throw Error('unexpected error') }
281+
return this._root.i(key, ...values)
282+
} else {
283+
return this._warnDefault(locale, key, ret, host)
284+
}
285+
}
286+
287+
i (key: Path, ...values: any): TranslateResult {
288+
if (!key) { return '' }
289+
290+
let locale: Locale = this.locale
291+
let index: number = 0
292+
if (typeof values[0] === 'string') {
293+
locale = values[0]
294+
index = 1
295+
}
296+
297+
const params: Array<any> = []
298+
for (let i = index; i < values.length; i++) {
299+
params.push(values[i])
300+
}
301+
302+
return this._i(key, locale, this.messages, null, ...params)
303+
}
304+
305+
_tc (
306+
key: Path,
307+
_locale: Locale,
308+
messages: LocaleMessages,
309+
host: any,
310+
choice?: number,
311+
...values: any
312+
): any {
256313
if (!key) { return '' }
257314
if (choice !== undefined) {
258315
return fetchChoice(this._t(key, _locale, messages, host, ...values), choice)

src/install.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { warn } from './util'
22
import extend from './extend'
33
import mixin from './mixin'
4+
import component from './component'
45

56
export let Vue
67

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

2627
extend(Vue)
2728
Vue.mixin(mixin)
29+
Vue.component(component.name, component)
2830

2931
// use object-based merge strategy
3032
const strats = Vue.config.optionMergeStrategies

test/unit/interpolation.test.js

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
const messages = {
2+
en: {
3+
text: 'one: {0}',
4+
premitive: 'one: {0}, two: {1}',
5+
component: 'root: {0}, component: {1}',
6+
link: '@:premitive'
7+
},
8+
ja: {
9+
text: '一: {0}',
10+
}
11+
}
12+
const components = {
13+
comp: {
14+
props: {
15+
msg: { type: String, default: '' }
16+
},
17+
render (h) {
18+
return h('p', [this.msg])
19+
}
20+
}
21+
}
22+
23+
describe('component interpolation', () => {
24+
let i18n
25+
beforeEach(() => {
26+
i18n = new VueI18n({
27+
locale: 'en',
28+
messages
29+
})
30+
})
31+
32+
describe('children', () => {
33+
describe('text nodes', () => {
34+
it('should be interpolated', done => {
35+
const el = document.createElement('div')
36+
const vm = new Vue({
37+
i18n,
38+
render (h) {
39+
return h('p', {}, [
40+
h('i18n', { props: { path: 'text' } }, [
41+
this._v('1')
42+
])
43+
])
44+
}
45+
}).$mount(el)
46+
nextTick(() => {
47+
assert.equal(vm.$el.textContent, 'one: 1')
48+
}).then(done)
49+
})
50+
})
51+
52+
describe('premitive nodes', () => {
53+
it('should be interpolated', done => {
54+
const el = document.createElement('div')
55+
const vm = new Vue({
56+
i18n,
57+
render (h) {
58+
return h('div', {}, [
59+
h('i18n', { props: { path: 'premitive' } }, [
60+
h('p', ['1']),
61+
h('p', ['2'])
62+
])
63+
])
64+
}
65+
}).$mount(el)
66+
nextTick(() => {
67+
assert.equal(vm.$el.innerHTML, 'one: <p>1</p>, two: <p>2</p>')
68+
}).then(done)
69+
})
70+
})
71+
72+
describe('components', () => {
73+
it('should be interpolated', done => {
74+
const el = document.createElement('div')
75+
const vm = new Vue({
76+
i18n,
77+
components,
78+
render (h) {
79+
return h('div', {}, [
80+
h('i18n', { props: { path: 'component' } }, [
81+
h('p', ['1']),
82+
h('comp', { props: { msg: 'foo' } })
83+
])
84+
])
85+
}
86+
}).$mount(el)
87+
nextTick(() => {
88+
assert.equal(vm.$el.innerHTML, 'root: <p>1</p>, component: <p>foo</p>')
89+
}).then(done)
90+
})
91+
})
92+
93+
describe('nested components', () => {
94+
it('should be interpolated', done => {
95+
const el = document.createElement('div')
96+
const vm = new Vue({
97+
i18n,
98+
components,
99+
render (h) {
100+
return h('div', {}, [
101+
h('i18n', { props: { path: 'component' } }, [
102+
h('p', ['1']),
103+
h('div', {}, [
104+
h('i18n', { props: { path: 'component' } }, [
105+
h('p', ['2']),
106+
h('comp', { props: { msg: 'nested' } })
107+
])
108+
])
109+
])
110+
])
111+
}
112+
}).$mount(el)
113+
nextTick(() => {
114+
assert.equal(
115+
vm.$el.innerHTML,
116+
'root: <p>1</p>, component: <div>root: <p>2</p>, component: <p>nested</p></div>'
117+
)
118+
}).then(done)
119+
})
120+
})
121+
})
122+
123+
describe('linked', () => {
124+
it('should be interpolated', done => {
125+
const el = document.createElement('div')
126+
const vm = new Vue({
127+
i18n,
128+
render (h) {
129+
return h('p', {}, [
130+
h('i18n', { props: { path: 'link' } }, [
131+
h('p', ['1']),
132+
h('p', ['2'])
133+
])
134+
])
135+
}
136+
}).$mount(el)
137+
nextTick(() => {
138+
assert.equal(vm.$el.innerHTML, 'one: <p>1</p>, two: <p>2</p>')
139+
}).then(done)
140+
})
141+
})
142+
143+
describe('locale', () => {
144+
it('should be interpolated', done => {
145+
const el = document.createElement('div')
146+
const vm = new Vue({
147+
i18n,
148+
render (h) {
149+
return h('p', {}, [
150+
h('i18n', { props: { path: 'text', locale: 'ja' } }, [
151+
this._v('1')
152+
])
153+
])
154+
}
155+
}).$mount(el)
156+
nextTick(() => {
157+
assert.equal(vm.$el.textContent, '一: 1')
158+
}).then(done)
159+
})
160+
})
161+
})

0 commit comments

Comments
 (0)