Skip to content
This repository was archived by the owner on May 19, 2022. It is now read-only.

Commit d141a7f

Browse files
authored
Merge pull request #41 from panter/features/wait
Features/wait
2 parents 7ef21d6 + cc33965 commit d141a7f

File tree

7 files changed

+370
-54
lines changed

7 files changed

+370
-54
lines changed

docs/guide/directive.md

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
# Directive
1+
# Directives
2+
3+
## v-t
24

35
Full Featured properties:
46

@@ -33,3 +35,30 @@ Vue.component("app", {
3335
template: `<p ref="text" v-t="{ path: 'helloWithName', language: 'en', args: { name: 'Hans' } }"></p>`
3436
});
3537
```
38+
39+
40+
## v-waitForT
41+
42+
Wait for the i18next fot be initialized. If not initialized it sets the element to `hidden = true` and wait
43+
for i18next to be initialized.
44+
45+
```javascript
46+
const locales = {
47+
en: {
48+
hello: "Hello"
49+
}
50+
};
51+
52+
i18next.init({
53+
lng: "en",
54+
resources: {
55+
en: { translation: locales.en }
56+
}
57+
});
58+
59+
const i18n = new VueI18next(i18next);
60+
61+
Vue.component("app", {
62+
template: `<p v-waitForT>$t("hello")</p>`
63+
});
64+
```

examples/app.js

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,28 +31,32 @@ const i18n = new VueI18next(i18next);
3131

3232
Vue.component('app', {
3333
template: `
34+
<div>
3435
<div>
35-
<div>
3636
<h3>Translation</h3>
3737
<language-changer></language-changer><load-bundle></load-bundle>
3838
<p>$t: {{ $t("message.hello") }}</p>
39-
</div>
40-
<div>
41-
<h3>Interpolation</h3>
42-
<i18next path="term" tag="label" for="tos">
43-
<a href="#" target="_blank">{{ $t("tos") }}</a>
44-
<strong>a</strong>
45-
</i18next>
46-
</div>
47-
<div>
48-
<h3>Prefix</h3>
49-
<key-prefix></key-prefix>
50-
</div>
51-
<div>
52-
<h3>Interpolation</h3>
53-
<inline-translations></inline-translations>
54-
</div>
55-
</div>`,
39+
</div>
40+
<div>
41+
<h3>Interpolation</h3>
42+
<i18next path="term" tag="label" for="tos">
43+
<a href="#" target="_blank">{{ $t("tos") }}</a>
44+
<strong>a</strong>
45+
</i18next>
46+
</div>
47+
<div>
48+
<h3>Prefix</h3>
49+
<key-prefix></key-prefix>
50+
</div>
51+
<div>
52+
<h3>Inline translations</h3>
53+
<inline-translations></inline-translations>
54+
</div>
55+
<div>
56+
<h3>Directive</h3>
57+
<with-directive></with-directive>
58+
</div>
59+
</div>`,
5660
});
5761

5862
Vue.component('language-changer', {
@@ -113,6 +117,11 @@ Vue.component('inline-translations', {
113117
</div>`,
114118
});
115119

120+
Vue.component('with-directive', {
121+
template: `
122+
<div v-t="{path:'message.hello'}"></div>`,
123+
});
124+
116125
new Vue({
117126
i18n,
118127
}).$mount('#app');

src/directive.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,8 @@ export function update(el, binding, vnode, oldVNode) {
8686

8787
t(el, binding, vnode);
8888
}
89+
90+
export default {
91+
bind,
92+
update,
93+
};

src/install.js

Lines changed: 42 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
/* eslint-disable import/no-mutable-exports */
22
import deepmerge from 'deepmerge';
33
import component from './component';
4-
import { bind, update } from './directive';
4+
import directive from './directive';
5+
import waitDirective from './wait';
56

67
export let Vue;
78

@@ -15,10 +16,10 @@ export function install(_Vue) {
1516

1617
const getByKey = (i18nOptions, i18nextOptions) => (key) => {
1718
if (
18-
i18nOptions &&
19-
i18nOptions.keyPrefix &&
20-
!key.includes(i18nextOptions.nsSeparator)
21-
) {
19+
i18nOptions &&
20+
i18nOptions.keyPrefix &&
21+
!key.includes(i18nextOptions.nsSeparator)
22+
) {
2223
return `${i18nOptions.keyPrefix}.${key}`;
2324
}
2425
return key;
@@ -39,38 +40,18 @@ export function install(_Vue) {
3940
};
4041

4142
Vue.mixin({
42-
computed: {
43-
$t() {
44-
const getKey = getByKey(
45-
this._i18nOptions,
46-
this.$i18n ? this.$i18n.i18next.options : {},
47-
);
48-
49-
if (this._i18nOptions && this._i18nOptions.namespaces) {
50-
const { lng, namespaces } = this._i18nOptions;
51-
52-
const fixedT = this.$i18n.i18next.getFixedT(lng, namespaces);
53-
return (key, options) =>
54-
fixedT(getKey(key), options, this.$i18n.i18nLoadedAt);
55-
}
56-
57-
return (key, options) =>
58-
this.$i18n.i18next.t(getKey(key), options, this.$i18n.i18nLoadedAt);
59-
},
60-
},
61-
6243
beforeCreate() {
6344
const options = this.$options;
6445
if (options.i18n) {
65-
this.$i18n = options.i18n;
46+
this._i18n = options.i18n;
6647
} else if (options.parent && options.parent.$i18n) {
67-
this.$i18n = options.parent.$i18n;
48+
this._i18n = options.parent.$i18n;
6849
}
6950
let inlineTranslations = {};
7051

71-
if (this.$i18n) {
52+
if (this._i18n) {
7253
const getNamespace =
73-
this.$i18n.options.getComponentNamespace || getComponentNamespace;
54+
this._i18n.options.getComponentNamespace || getComponentNamespace;
7455
const { namespace, loadNamespace } = getNamespace(this);
7556

7657
if (options.__i18n) {
@@ -89,7 +70,7 @@ export function install(_Vue) {
8970
messages,
9071
} = this.$options.i18nOptions;
9172
let { namespaces } = this.$options.i18nOptions;
92-
namespaces = namespaces || this.$i18n.i18next.options.defaultNS;
73+
namespaces = namespaces || this._i18n.i18next.options.defaultNS;
9374

9475
if (typeof namespaces === 'string') namespaces = [namespaces];
9576
const namespacesToLoad = namespaces.concat([namespace]);
@@ -99,7 +80,7 @@ export function install(_Vue) {
9980
}
10081

10182
this._i18nOptions = { lng, namespaces: namespacesToLoad, keyPrefix };
102-
this.$i18n.i18next.loadNamespaces(namespaces);
83+
this._i18n.i18next.loadNamespaces(namespaces);
10384
} else if (options.parent && options.parent._i18nOptions) {
10485
this._i18nOptions = { ...options.parent._i18nOptions };
10586
this._i18nOptions.namespaces = [
@@ -110,13 +91,13 @@ export function install(_Vue) {
11091
this._i18nOptions = { namespaces: [namespace] };
11192
}
11293

113-
if (loadNamespace && this.$i18n.options.loadComponentNamespace) {
114-
this.$i18n.i18next.loadNamespaces([namespace]);
94+
if (loadNamespace && this._i18n.options.loadComponentNamespace) {
95+
this._i18n.i18next.loadNamespaces([namespace]);
11596
}
11697

11798
const languages = Object.keys(inlineTranslations);
11899
languages.forEach((lang) => {
119-
this.$i18n.i18next.addResourceBundle(
100+
this._i18n.i18next.addResourceBundle(
120101
lang,
121102
namespace,
122103
{ ...inlineTranslations[lang] },
@@ -125,9 +106,35 @@ export function install(_Vue) {
125106
);
126107
});
127108
}
109+
110+
const getKey = getByKey(
111+
this._i18nOptions,
112+
this._i18n ? this._i18n.i18next.options : {},
113+
);
114+
115+
if (this._i18nOptions && this._i18nOptions.namespaces) {
116+
const { lng, namespaces } = this._i18nOptions;
117+
118+
const fixedT = this._i18n.i18next.getFixedT(lng, namespaces);
119+
this._getI18nKey = (key, i18nextOptions) =>
120+
fixedT(getKey(key), i18nextOptions, this._i18n.i18nLoadedAt);
121+
} else {
122+
this._getI18nKey = (key, i18nextOptions) =>
123+
this._i18n.t(getKey(key), i18nextOptions, this._i18n.i18nLoadedAt);
124+
}
128125
},
129126
});
130127

128+
// extend Vue.js
129+
Object.defineProperty(Vue.prototype, '$i18n', {
130+
get() { return this._i18n; },
131+
});
132+
133+
Vue.prototype.$t = function t(key, options) {
134+
return this._getI18nKey(key, options);
135+
};
136+
131137
Vue.component(component.name, component);
132-
Vue.directive('t', { bind, update });
138+
Vue.directive('t', directive);
139+
Vue.directive('waitForT', waitDirective);
133140
}

src/wait.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/* eslint-disable no-param-reassign, no-unused-vars */
2+
3+
import { warn } from './utils';
4+
5+
function assert(vnode) {
6+
const vm = vnode.context;
7+
8+
if (!vm.$i18n) {
9+
warn('No VueI18Next instance found in the Vue instance');
10+
return false;
11+
}
12+
13+
return true;
14+
}
15+
16+
function waitForIt(el, vnode) {
17+
if (vnode.context.$i18n.i18next.isInitialized) {
18+
el.hidden = false;
19+
} else {
20+
el.hidden = true;
21+
const initialized = () => {
22+
vnode.context.$forceUpdate();
23+
// due to emitter removing issue in i18next we need to delay remove
24+
setTimeout(() => {
25+
if (vnode.context && vnode.context.$i18n) {
26+
vnode.context.$i18n.i18next.off('initialized', initialized);
27+
}
28+
}, 1000);
29+
};
30+
vnode.context.$i18n.i18next.on('initialized', initialized);
31+
}
32+
}
33+
34+
export function bind(el, binding, vnode) {
35+
if (!assert(vnode)) {
36+
return;
37+
}
38+
39+
waitForIt(el, vnode);
40+
}
41+
42+
export function update(el, binding, vnode, oldVNode) {
43+
if (vnode.context.$i18n.i18next.isInitialized) {
44+
el.hidden = false;
45+
}
46+
}
47+
48+
export default {
49+
bind,
50+
update,
51+
};

test/unit/component.test.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,15 @@ describe('Components with backend', () => {
311311

312312
expect(root.textContent).to.equal('dev__common__test');
313313
});
314+
315+
it('should wait for translation to be ready', async () => {
316+
const root = vm.$refs.hello;
317+
expect(root.textContent).to.equal('key1');
318+
backend.flush();
319+
await nextTick();
320+
321+
expect(root.textContent).to.equal('dev__common__test');
322+
});
314323
});
315324

316325
describe('Nested namespaces', () => {

0 commit comments

Comments
 (0)