diff --git a/decls/i18n.js b/decls/i18n.js index 83d3193c2..8b468c610 100644 --- a/decls/i18n.js +++ b/decls/i18n.js @@ -65,6 +65,7 @@ declare type I18nOptions = { pluralizationRules?: { [lang: string]: (choice: number, choicesLength: number) => number, }, + preserveDirectiveContent?: boolean, }; declare type IntlAvailability = { @@ -106,7 +107,8 @@ declare interface I18n { n (value: number, ...args: any): NumberFormatResult, pluralizationRules: { [lang: string]: (choice: number, choicesLength: number) => number - } + }, + preserveDirectiveContent: boolean }; declare interface Formatter { diff --git a/examples/directive/index.html b/examples/directive/index.html index 723645f48..51019d274 100644 --- a/examples/directive/index.html +++ b/examples/directive/index.html @@ -15,6 +15,17 @@

+
+

+ + + + + + +

+ +
+ diff --git a/src/directive.js b/src/directive.js index b7b2d9f87..2b6da6d7e 100644 --- a/src/directive.js +++ b/src/directive.js @@ -26,7 +26,10 @@ export function unbind (el: any, binding: Object, vnode: any, oldVNode: any): vo return } - el.textContent = '' + const i18n: any = vnode.context.$i18n || {} + if (!binding.modifiers.preserve && !i18n.preserveDirectiveContent) { + el.textContent = '' + } el._vt = undefined delete el['_vt'] el._locale = undefined diff --git a/src/index.js b/src/index.js index ce7492587..a7629630c 100644 --- a/src/index.js +++ b/src/index.js @@ -61,6 +61,7 @@ export default class VueI18n { pluralizationRules: { [lang: string]: (choice: number, choicesLength: number) => number } + preserveDirectiveContent: boolean constructor (options: I18nOptions = {}) { // Auto install if it is not done yet and `window` has `Vue`. @@ -94,6 +95,9 @@ export default class VueI18n { this._dataListeners = [] this.pluralizationRules = options.pluralizationRules || {} + this.preserveDirectiveContent = options.preserveDirectiveContent === undefined + ? false + : !!options.preserveDirectiveContent this._exist = (message: Object, key: Path): boolean => { if (!message || !key) { return false } diff --git a/src/mixin.js b/src/mixin.js index cbf2caad7..55fae110a 100644 --- a/src/mixin.js +++ b/src/mixin.js @@ -38,6 +38,7 @@ export default { options.i18n.fallbackLocale = this.$root.$i18n.fallbackLocale options.i18n.silentTranslationWarn = this.$root.$i18n.silentTranslationWarn options.i18n.pluralizationRules = this.$root.$i18n.pluralizationRules + options.i18n.preserveDirectiveContent = this.$root.$i18n.preserveDirectiveContent } // init locale messages via custom blocks diff --git a/test/unit/directive.test.js b/test/unit/directive.test.js index 8042f7f96..418f0b7e9 100644 --- a/test/unit/directive.test.js +++ b/test/unit/directive.test.js @@ -199,5 +199,80 @@ describe('custom directive', () => { }).then(done) }) }) + + describe('preserve content', () => { + it('should clear element content on destroy by default', done => { + const vm = createVM({ + i18n, + data: () => ({ visible: true }), + render (h) { + //

+ const directives = this.visible ? [{ + name: 't', rawName: 'v-t', value: ('message.hello'), expression: "'message.hello'" + }] : [] + return h('p', { ref: 'text', directives }) + } + }) + + nextTick(() => { + assert.strictEqual(vm.$refs.text.textContent, messages.en.message.hello) + + vm.visible = false + vm.$forceUpdate() + }).then(() => { + assert.strictEqual(vm.$refs.text.textContent, '') + }).then(done) + }) + + it('should not clear element content with "preserve" modifier', done => { + const vm = createVM({ + i18n, + data: () => ({ visible: true }), + render (h) { + //

+ const directives = this.visible ? [{ + name: 't', rawName: 'v-t', value: ('message.hello'), expression: "'message.hello'", modifiers: { preserve: true } + }] : [] + return h('p', { ref: 'text', directives }) + } + }) + + nextTick(() => { + assert.strictEqual(vm.$refs.text.textContent, messages.en.message.hello) + + vm.visible = false + vm.$forceUpdate() + }).then(() => { + assert.strictEqual(vm.$refs.text.textContent, messages.en.message.hello) + }).then(done) + }) + + it('should not clear element content when "preserveDirectiveContent" i18nOption is set to true', done => { + const vm = createVM({ + i18n: new VueI18n({ + locale: 'en', + messages, + preserveDirectiveContent: true + }), + data: () => ({ visible: true }), + render (h) { + //

+ const directives = this.visible ? [{ + name: 't', rawName: 'v-t', value: ('message.hello'), expression: "'message.hello'" + }] : [] + return h('p', { ref: 'text', directives }) + } + }) + + nextTick(() => { + assert.strictEqual(vm.$refs.text.textContent, messages.en.message.hello) + + vm.visible = false + vm.$forceUpdate() + }).then(() => { + assert.strictEqual(vm.$refs.text.textContent, messages.en.message.hello) + }).then(done) + }) + }) }) }) diff --git a/types/index.d.ts b/types/index.d.ts index 496e1b178..f147435fa 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -93,6 +93,7 @@ declare namespace VueI18n { sync?: boolean; silentTranslationWarn?: boolean; pluralizationRules?: PluralizationRulesMap; + preserveDirectiveContent?: boolean; } } @@ -129,6 +130,7 @@ export declare interface IVueI18n { formatter: VueI18n.Formatter; silentTranslationWarn: boolean; pluralizationRules: VueI18n.PluralizationRulesMap; + preserveDirectiveContent: boolean; } declare class VueI18n { diff --git a/types/test/index.ts b/types/test/index.ts index 144e4e79c..911000274 100644 --- a/types/test/index.ts +++ b/types/test/index.ts @@ -63,6 +63,7 @@ const i18n = new VueI18n({ fallbackRoot: false, sync: true, silentTranslationWarn: true, + preserveDirectiveContent: true, }); i18n.messages[locale][key]; // $ExpectType LocaleMessage i18n.dateTimeFormats[locale][key]; // $ExpectType DateTimeFormatOptions @@ -72,6 +73,7 @@ i18n.fallbackLocale; // $ExpectType string i18n.missing; // $ExpectType MissingHandler i18n.formatter; // $ExpectType Formatter i18n.silentTranslationWarn; // $ExpectType boolean +i18n.preserveDirectiveContent; // $ExpectType boolean i18n.setLocaleMessage; // $ExpectType (locale: string, message: LocaleMessageObject) => void i18n.getLocaleMessage; // $ExpectType (locale: string) => LocaleMessageObject i18n.mergeLocaleMessage; // $ExpectType (locale: string, message: LocaleMessageObject) => void diff --git a/vuepress/api/README.md b/vuepress/api/README.md index 6090b1d51..f483150d9 100644 --- a/vuepress/api/README.md +++ b/vuepress/api/README.md @@ -247,7 +247,7 @@ Whether synchronize the root level locale to the component localization locale. If `false`, regardless of the root level locale, localize for each component locale. -### silentTranslationWarn +#### silentTranslationWarn > 6.1+ @@ -259,6 +259,16 @@ Whether suppress warnings outputted when localization fails. If `true`, supress localization fail warnings. +#### preserveDirectiveContent + +> 8.7+ + + * **Type:** `Boolean` + + * **Default:** `false` + +Whether `v-t` directive's element should preserve `textContent` after directive is unbinded. + ### Properties #### locale @@ -331,6 +341,16 @@ The formatter that implemented with `Formatter` interface. Whether suppress warnings outputted when localization fails. +#### preserveDirectiveContent + +> 8.7+ + + * **Type:** `boolean` + + * **Read/Write** + +Whether `v-t` directive's element should preserve `textContent` after directive is unbinded. + ### Methods #### getChoiceIndex @@ -338,7 +358,7 @@ Whether suppress warnings outputted when localization fails. * **Arguments:** * `{number} choice` * `{number} choicesLength` - + * **Return:** `finalChoice {number}` Get pluralization index for current pluralizing number and a given amount of choices. Can be overriden through prototype mutation: @@ -519,6 +539,10 @@ This is the same as `$n` method of Vue instance method. More detail see [$n](#n) * **Expects:** `string | Object` + * **Modifiers:** + + * `.preserve`: (8.7.0+) preserves element `textContent` when directive is unbinded. + * **Details:** Update the element `textContent` that localized with locale messages. You can use string syntax or object syntax. string syntax can be specified as a keypath of locale messages. If you can be used object syntax, you need to specify as the object key the following params: @@ -526,6 +550,10 @@ Update the element `textContent` that localized with locale messages. You can us * path: required, key of locale messages * locale: optional, locale * args: optional, for list or named formatting + +::::tip NOTE +The element `textContent` will be cleared by default when `v-t` directive is unbinded. This might be undesirable situation when used inside [transitions](https://vuejs.org/v2/guide/transitions.html). To preserve `textContent` data after directive unbind use `.preserve` modifier or global [`preserveDirectiveContent` option](#preservedirectivecontent). +:::: * **Examples:** ```html @@ -539,6 +567,9 @@ Update the element `textContent` that localized with locale messages. You can us

+ + +

``` * **See also:** [Custom directive localization](../guide/directive.md) diff --git a/vuepress/guide/directive.md b/vuepress/guide/directive.md index 7b52ee57a..56bd68d73 100644 --- a/vuepress/guide/directive.md +++ b/vuepress/guide/directive.md @@ -88,6 +88,71 @@ Outputs: ``` +## Use with transitions + +:::tip Support Version +:new: 8.7+ +::: + +When `v-t` directive is applied to an element inside [`` component](https://vuejs.org/v2/api/#transition), you may notice that translated message will disappear during the transition. This behavior is related to the nature of the `` component implementation – all directives in disappearing element inside `` component will be destroyed **before transition starts**. This behavior may result in content flickering on short animations, but most noticable on long transitions. + +To make sure directive content will stay un-touched during transition just add [`.preserve` modifier](../api/#v-t) to `v-t` directive defintion. + +Javascript: + +```js +new Vue({ + i18n: new VueI18n({ + locale: 'en', + messages: { + en: { preserve: 'with preserve' }, + } + }), + data: { toggle: true } +}).$mount('#in-transitions') +``` + +Templates: + +```html +
+ + + + +
+``` + +It is also possible to set global setting on `VueI18n` instance itself, which will have effect on all `v-t` directives without modifier. + +Javascript: + +```js +new Vue({ + i18n: new VueI18n({ + locale: 'en', + messages: { + en: { preserve: 'with preserve' }, + }, + preserveDirectiveContent: true + }), + data: { toggle: true } +}).$mount('#in-transitions') +``` + +Templates: + +```html +
+ + + + +
+``` + +About the above examples, see the [example](https://github.com/kazupon/vue-i18n/tree/dev/examples/directive) + ## `$t` vs `v-t` ### `$t`