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

Commit b5b84d8

Browse files
mochettskazupon
authored andcommitted
⭐ new: add pluralization (#44) by @mmochetti
1 parent 3e1bf8d commit b5b84d8

3 files changed

Lines changed: 262 additions & 1 deletion

File tree

src/extend.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,14 @@ export default function (Vue) {
7979
return this.$options.locales[lang]
8080
}
8181

82+
function fetchChoice (locale, choice) {
83+
if (!locale && typeof locale !== 'string') return null
84+
const choices = locale.split('|')
85+
choice = choice - 1
86+
if (!choices[choice]) return locale
87+
return choices[choice].trim()
88+
}
89+
8290
/**
8391
* Vue.t
8492
*
@@ -94,6 +102,19 @@ export default function (Vue) {
94102
|| warnDefault(key)
95103
}
96104

105+
/**
106+
* Vue.tc
107+
*
108+
* @param {String} key
109+
* @param {number|undefined} choice
110+
* @param {Array} ...args
111+
* @return {String}
112+
*/
113+
114+
Vue.tc = (key, choice, ...args) => {
115+
if (!choice) { choice = 1 }
116+
return fetchChoice(Vue.t(key, ...args), choice)
117+
}
97118

98119
/**
99120
* $t
@@ -115,5 +136,20 @@ export default function (Vue) {
115136
|| warnDefault(key)
116137
}
117138

139+
/**
140+
* $tc
141+
*
142+
* @param {String} key
143+
* @param {number|undefined} choice
144+
* @param {Array} ...args
145+
* @return {String}
146+
*/
147+
148+
Vue.prototype.$tc = function (key, choice, ...args) {
149+
if (typeof choice !== 'number' && typeof choice !== 'undefined') return key
150+
if (!choice) { choice = 1 }
151+
return fetchChoice(this.$t(key, ...args), choice)
152+
}
153+
118154
return Vue
119155
}

test/specs/fixture/locales.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,15 @@ export default {
1212
'hello world': 'Hello World',
1313
'Hello {0}': 'Hello {0}',
1414
'continue-with-new-account': 'continue with new account',
15-
underscore: '{hello_msg} world'
15+
underscore: '{hello_msg} world',
16+
plurals: {
17+
car: 'car | cars',
18+
format: {
19+
named: 'Hello {name}, how are you? | Hi {name}, you look fine',
20+
list: 'Hello {0}, how are you? | Hi {0}, you look fine'
21+
},
22+
fallback: 'this is fallback | this is a plural fallback'
23+
}
1624
},
1725
ja: {
1826
message: {
@@ -23,6 +31,14 @@ export default {
2331
list: 'こんにちは {0}, ごきげんいかが?'
2432
},
2533
fallback1: 'これはフォールバック'
34+
},
35+
plurals: {
36+
car: 'ザ・ワールド | これはフォールバック',
37+
format: {
38+
named: 'こんにちは {name}, ごきげんいかが? | こんにちは {name}, ごきげんいかが?',
39+
list: 'こんにちは {0}, ごきげんいかが?| こんにちは {0}, ごきげんいかが?'
40+
},
41+
fallback: 'これはフォールバック | ザ・ワールド'
2642
}
2743
}
2844
}

test/specs/i18n.js

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,106 @@ describe('i18n', () => {
109109
})
110110
})
111111

112+
describe('Vue.tc', () => {
113+
describe('en language locale', () => {
114+
it('should translate an english', () => {
115+
assert.equal(Vue.tc('plurals.car', 1), 'car')
116+
})
117+
})
118+
119+
describe('multi plural check', () => {
120+
it('should fetch pluralized string', () => {
121+
assert.equal(Vue.tc('plurals.car', 2), 'cars')
122+
})
123+
})
124+
125+
describe('ja language locale', () => {
126+
it('should translate a japanese', () => {
127+
assert.equal(Vue.tc('plurals.car', 1, 'ja'), 'ザ・ワールド')
128+
})
129+
})
130+
131+
describe('key argument', () => {
132+
describe('not specify', () => {
133+
it('should return empty string', () => {
134+
assert.equal(Vue.tc(), '')
135+
})
136+
})
137+
138+
describe('empty string', () => {
139+
it('should return empty string', () => {
140+
assert.equal(Vue.tc(''), '')
141+
})
142+
})
143+
144+
describe('not regist key', () => {
145+
it('should return key string', () => {
146+
assert.equal(Vue.tc('foo.bar'), 'foo.bar')
147+
})
148+
})
149+
150+
describe('sentence fragment', () => {
151+
it('should translate fragment', () => {
152+
assert.equal(Vue.tc('hello world'), 'Hello World')
153+
})
154+
155+
it('should return replaced string if available', () => {
156+
assert.equal(
157+
Vue.tc('Hello {0}', 1, ['kazupon']),
158+
'Hello kazupon'
159+
)
160+
})
161+
162+
it('should return key if unavailable', () => {
163+
assert.equal(Vue.tc('Hello'), 'Hello')
164+
})
165+
})
166+
})
167+
168+
describe('format arguments', () => {
169+
context('named', () => {
170+
it('should return replaced string', () => {
171+
assert.equal(
172+
Vue.tc('plurals.format.named', 1, { name: 'kazupon' }),
173+
'Hello kazupon, how are you?'
174+
)
175+
})
176+
})
177+
178+
context('list', () => {
179+
it('should return replaced string', () => {
180+
assert.equal(
181+
Vue.tc('plurals.format.list', 1, ['kazupon']),
182+
'Hello kazupon, how are you?'
183+
)
184+
})
185+
})
186+
})
187+
188+
describe('language argument', () => {
189+
it('should return empty string', () => {
190+
assert.equal(Vue.tc('plurals.car', 1, 'ja'), 'ザ・ワールド')
191+
})
192+
})
193+
194+
describe('format & language arguments', () => {
195+
it('should return replaced string', () => {
196+
assert.equal(
197+
Vue.tc('plurals.format.list', 1, 'ja', ['kazupon']),
198+
'こんにちは kazupon, ごきげんいかが?'
199+
)
200+
})
201+
})
202+
203+
describe('fallback', () => {
204+
it('should return fallback string', () => {
205+
assert.equal(
206+
Vue.tc('plurals.fallback', 1, 'ja'),
207+
'これはフォールバック'
208+
)
209+
})
210+
})
211+
})
112212

113213
describe('$t', () => {
114214
describe('en language locale', () => {
@@ -219,6 +319,115 @@ describe('i18n', () => {
219319
})
220320

221321

322+
describe('$tc', () => {
323+
describe('en language locale', () => {
324+
it('should translate plural english', () => {
325+
const vm = new Vue()
326+
assert.equal(vm.$tc('plurals.car', 1), 'car')
327+
})
328+
})
329+
330+
describe('multi plural check', () => {
331+
it('should fetch pluralized string', () => {
332+
const vm = new Vue()
333+
assert.equal(vm.$tc('plurals.car', 2), 'cars')
334+
})
335+
})
336+
337+
describe('key argument', () => {
338+
describe('not specify', () => {
339+
it('should return empty string', () => {
340+
const vm = new Vue()
341+
assert.equal(vm.$tc(), '')
342+
})
343+
})
344+
345+
describe('empty string', () => {
346+
it('should return empty string', () => {
347+
const vm = new Vue()
348+
assert.equal(vm.$tc(''), '')
349+
})
350+
})
351+
352+
describe('not regist key', () => {
353+
it('should return key string', () => {
354+
const vm = new Vue()
355+
assert.equal(vm.$tc('foo.bar'), 'foo.bar')
356+
})
357+
})
358+
359+
describe('sentence fragment', () => {
360+
it('should translate fragment', () => {
361+
const vm = new Vue()
362+
assert.equal(vm.$tc('hello world'), 'Hello World')
363+
})
364+
365+
it('should return replaced string if available', () => {
366+
const vm = new Vue()
367+
assert.equal(
368+
vm.$tc('Hello {0}', 1, ['kazupon']),
369+
'Hello kazupon'
370+
)
371+
})
372+
373+
it('should return key if unavailable', () => {
374+
const vm = new Vue()
375+
assert.equal(vm.$tc('Hello'), 'Hello')
376+
})
377+
})
378+
})
379+
380+
describe('format arguments', () => {
381+
context('named', () => {
382+
it('should return replaced string', () => {
383+
const vm = new Vue()
384+
assert.equal(
385+
vm.$tc('plurals.format.named', 1, { name: 'kazupon' }),
386+
'Hello kazupon, how are you?'
387+
)
388+
})
389+
})
390+
391+
context('list', () => {
392+
it('should return replaced string', () => {
393+
const vm = new Vue()
394+
assert.equal(
395+
vm.$tc('plurals.format.list', 1, ['kazupon']),
396+
'Hello kazupon, how are you?'
397+
)
398+
})
399+
})
400+
})
401+
402+
describe('language argument', () => {
403+
it('should return empty string', () => {
404+
const vm = new Vue()
405+
assert.equal(vm.$tc('plurals.car', 1, 'ja'), 'ザ・ワールド')
406+
})
407+
})
408+
409+
describe('format & language arguments', () => {
410+
it('should return replaced string', () => {
411+
const vm = new Vue()
412+
assert.equal(
413+
vm.$tc('plurals.format.list', 1, 'ja', ['kazupon']),
414+
'こんにちは kazupon, ごきげんいかが?'
415+
)
416+
})
417+
})
418+
419+
describe('fallback', () => {
420+
it('should return fallback string', () => {
421+
const vm = new Vue()
422+
assert.equal(
423+
vm.$tc('plurals.fallback', 2, 'ja'),
424+
'ザ・ワールド'
425+
)
426+
})
427+
})
428+
})
429+
430+
222431
describe('reactive translation', () => {
223432
let el
224433
beforeEach(() => {

0 commit comments

Comments
 (0)